Data validation is very important. It conforms to the expected format, type, range, and business rules, as well as identify and prevent errors, inconsistencies, and fraud. When we need to perform data validation that cannot be handled by the built-in validation annotations provided by Spring Framework, we can use custom validation to define our own rules and constraints. In this tutorial, I will show you how to implement custom Validation annotation in Spring Boot example (with Rest API).
More Practice:
– Spring Boot Validation annotations
– Spring Boot Login and Registration example
– Spring Boot 3 Rest API CRUD example
– Spring Boot @ControllerAdvice & @ExceptionHandler example
– Spring Boot Unit Test for Rest Controller
– Documentation: Spring Boot Swagger 3 example
– Caching: Spring Boot Redis Cache example
– Spring Boot + GraphQL example
Contents
Spring Boot custom Validation example Overview
We will implement 2 Spring Boot custom validation annotations:
@StrongPassword
: check if string is 8 characters long and combination of uppercase letters, lowercase letters, numbers, special characters.@PasswordMatching
: check if the password and confirm password are the same.
Here are the responses if users send invalid request data:
– Custom Validation for Password:
– Custom Validation for Confirm Password:
Project Structure
Let me explain it briefly.
– StrongPassword.java
and PasswordMatching.java
define annotation interfaces for custom validation rules.
– StrongPasswordValidator.java
and PasswordMatchingValidator.java
define validator classes that implement the validation logic.
– SignupRequest
is the payload class (or DTO) that uses the custom validation annotations.
– UserController
is the Rest Controller that exports API endpoint for receiving SignupRequest
payload.
– ValidationExceptionHandler
handles error when the validation fails.
Setup Spring Boot project with Custom Validation
To use custom Validation annotation in Spring Boot, you need to add the spring-boot-starter-validation dependency to your Maven project’s pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Or Gradle project with build.gradle file:
implementation 'org.springframework.boot:spring-boot-starter-validation:3.0.6'
Create Custom Validation Annotation interface
In package validation, let’s create 2 new annotation interfaces that define custom validation rules using @Constraint
and @Target
annotations.
validation/StrongPassword.java
package com.bezkoder.spring.validation.custom.validation;
import java.lang.annotation.*;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Constraint(validatedBy = StrongPasswordValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StrongPassword {
String message() default "Must be 8 characters long and combination of uppercase letters, lowercase letters, numbers, special characters.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
validation/PasswordMatching.java
package com.bezkoder.spring.validation.custom.validation;
import java.lang.annotation.*;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
@Constraint(validatedBy = PasswordMatchingValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordMatching {
String password();
String confirmPassword();
String message() default "Passwords must match!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface List {
PasswordMatching[] value();
}
}
– The field message
is for error message.
– The @Constraint
annotation defines the actual validator implementation: StrongPasswordValidator
and PasswordMatchingValidator
.
– @Target({ ElementType.TYPE })
indicates that we will attach the validator to the whole class.
We have to define the password
and the confirmPassword
field to tell the validator which fields we want to compare.
Create Validator class
Next, also in validation package, we create the custom validator class that implements ConstraintValidator
interface for the validation logic.
We’ll check if string contains at least one digit, one lowercase letter, one uppercase letter, one special character and 8 characters long.
validation/StrongPasswordValidator.java
package com.bezkoder.spring.validation.custom.validation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// check if string contains at least one digit, one lowercase letter, one uppercase letter, one special character and 8 characters long
return value.matches("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!*()]).{8,}$");
}
}
We use String matches()
function with regular expression for a strong password:
^
: the start of the string(?=.*\d)
: at least one digit(?=.*[a-z])
: at least one lowercase letter(?=.*[A-Z])
: at least one uppercase letter(?=.*[@#$%^&+=!*()])
: at least one special character.{8,}
: at least 8 characters long$
: the end of the string
We continue to create custom validator class for validating multiple fields together.
validation/PasswordMatchingValidator.java
package com.bezkoder.spring.validation.custom.validation;
import org.springframework.beans.BeanWrapperImpl;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PasswordMatchingValidator implements ConstraintValidator<PasswordMatching, Object> {
private String password;
private String confirmPassword;
@Override
public void initialize(PasswordMatching matching) {
this.password = matching.password();
this.confirmPassword = matching.confirmPassword();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
Object passwordValue = new BeanWrapperImpl(value).getPropertyValue(password);
Object confirmPasswordValue = new BeanWrapperImpl(value).getPropertyValue(confirmPassword);
return (passwordValue != null) ? passwordValue.equals(confirmPasswordValue) : confirmPasswordValue == null;
}
}
The validator can read password
and confirmPassword
in the initialize()
method, then check them later on in the isValid()
.
Use the Custom Validation
We will use the @StrongPassword
annotation on a field in SignupRequest
class that needs to be validated, and @PasswordMatching
annotation for the whole class.
payload/SignupRequest.java
package com.bezkoder.spring.validation.custom.payload;
import com.bezkoder.spring.validation.custom.validation.PasswordMatching;
import com.bezkoder.spring.validation.custom.validation.StrongPassword;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@PasswordMatching(
password = "password",
confirmPassword = "confirmPassword",
message = "Password and Confirm Password must be matched!"
)
public class SignupRequest {
@NotBlank
@Size(min = 3, max = 20)
private String username;
@NotBlank
@Size(max = 50)
@Email
private String email;
@StrongPassword
private String password;
private String confirmPassword;
//getters and setters
}
Validate the Data
Now we need to add the @Valid
annotation to the field or method parameter we want to validate.
controller/UserController.java
package com.bezkoder.spring.validation.custom.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.bezkoder.spring.validation.custom.payload.SignupRequest;
import jakarta.validation.Valid;
@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api/auth")
public class UserController {
@PostMapping("/signup")
public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
return new ResponseEntity<>("User registered Successfully!", HttpStatus.OK);
}
}
Handle Custom Validation Exception
When the validation fails, Spring will throw an exception of type MethodArgumentNotValidException
and we need to handle it in ValidationExceptionHandler
class.
exception/ValidationExceptionHandler.java
package com.bezkoder.spring.validation.custom.exception;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import jakarta.servlet.http.HttpServletRequest;
@ControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> notValid(MethodArgumentNotValidException ex, HttpServletRequest request) {
List<String> errors = new ArrayList<>();
ex.getAllErrors().forEach(err -> errors.add(err.getDefaultMessage()));
Map<String, List<String>> result = new HashMap<>();
result.put("errors", errors);
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
}
For more detail about the technique, please visit:
Spring Boot @ControllerAdvice & @ExceptionHandler example
Conclusion
Today we’ve known step by step to implement Custom Validation in Spring Boot example. We created two Custom Validation annotations successfully for validating one field (strong password) and multiple fields (password and confirm password).
Happy Learning! See you again.
Source Code
You can find the complete source code for this tutorial on Github.
Further Reading
Fullstack CRUD App:
– Spring Boot Thymeleaf example
– Vue + Spring Boot example
– Angular 8 + Spring Boot example
– Angular 10 + Spring Boot example
– Angular 11 + Spring Boot example
– Angular 12 + Spring Boot example
– Angular 13 + Spring Boot example
– Angular 14 + Spring Boot example
– Angular 15 + Spring Boot example
– Angular 16 + Spring Boot example
– React + Spring Boot example
More Practice:
– Spring Boot – Validate Request Body
– Spring Boot Rest API example: CRUD Application
– Spring Boot Login and Registration example
– Spring Boot Rest XML example
– Spring Boot Multipart File upload example
– Spring Boot Pagination and Sorting example
– Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
– Caching: Spring Boot Redis Cache example
– Spring Boot + GraphQL example