Creating Custom Validators in Spring Boot with Jakarta Bean Validation
Validation is one of the most important aspects of application development. It ensures that the data entering our application is correct, consistent, and follows business rules. Jakarta Bean…
Validation is one of the most important aspects of application development. It ensures that the data entering our application is correct, consistent, and follows business rules.
Jakarta Bean Validation (formerly Java Bean Validation) provides many built-in validation annotations such as:
@NotNull@NotBlank@Size@Email@Min@Max@Pattern
These annotations cover most common validation requirements. However, in real-world applications, business requirements often go beyond these standard validations.
This is where custom validators come into the picture.
Why Do We Need Custom Validators?
Imagine you’re developing an employee management system.
The company has the following password policy:
- Minimum 8 characters
- At least one uppercase letter
- At least one number
- At least one special character
Can we achieve this using built-in annotations?
Technically yes, by writing a long regular expression with @Pattern.
Example:
@Pattern(
regexp = "^(?=.*[A-Z])(?=.*\\d)(?=.*[@#$%^&+=]).{8,}$",
message = "Invalid Password"
)
private String password;
Although this works, it has several disadvantages:
- Difficult to read
- Hard to maintain
- Cannot be reused easily
- Business logic is hidden inside a regex
Instead, we can create our own annotation:
@StrongPassword
private String password;
Now the code becomes clean and expressive.
What is a Custom Validator?
A custom validator consists of two parts:
- A custom annotation
- A validator class
Together they define:
- What to validate
- How to validate
Step 1: Create a Custom Annotation
Let’s create an annotation named @StrongPassword.
@Constraint(validatedBy = StrongPasswordValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StrongPassword {
String message() default "Weak Password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Understanding the Annotation
@Constraint
@Constraint(validatedBy = StrongPasswordValidator.class)
This tells Bean Validation:
“Whenever you find
@StrongPassword, useStrongPasswordValidatorto validate it.”
Without this annotation, Bean Validation doesn’t know which class contains the validation logic.
@Target
@Target(ElementType.FIELD)
Specifies where this annotation can be used.
Examples:
- Field
- Method
- Parameter
- Class
Since passwords are usually fields, we use:
FIELD
@Retention
@Retention(RetentionPolicy.RUNTIME)
Makes the annotation available at runtime so the validation framework can read it using reflection.
message()
String message() default "Weak Password";
Defines the default validation error message.
Example response:
Password : Weak Password
groups()
Used for validation grouping.
Most beginners can ignore this until working with advanced validation scenarios.
payload()
Allows attaching metadata to validation errors.
Rarely used in everyday applications.
Step 2: Create the Validator Class
Now we write the actual validation logic.
public class StrongPasswordValidator
implements ConstraintValidator<StrongPassword, String> {
@Override
public boolean isValid(
String password,
ConstraintValidatorContext context) {
if(password == null)
return false;
return password.length() >= 8
&& password.matches(".*[A-Z].*")
&& password.matches(".*\\d.*");
}
}
Understanding ConstraintValidator
The interface looks like this:
ConstraintValidator<AnnotationType, DataType>
In our example:
ConstraintValidator<StrongPassword, String>
Meaning:
- Validate the
@StrongPasswordannotation - The value being validated is of type
String
The isValid() Method
boolean isValid(
String value,
ConstraintValidatorContext context)
This method is automatically called whenever validation occurs.
It returns:
true→ Validation passedfalse→ Validation failed
How Does Validation Work?
Suppose we have:
public class User {
@StrongPassword
private String password;
}
When Spring Boot receives a request:
{
"password":"hello"
}
The validation flow is:
Request
│
â–¼
@Valid
│
â–¼
Find @StrongPassword
│
â–¼
StrongPasswordValidator
│
â–¼
isValid(password)
│
â–¼
true / false
│
â–¼
Validation Result
If the password is weak:
{
"password":"Weak Password"
}
Real-World Use Cases for Custom Validators
Custom validators are useful whenever business rules cannot be expressed with built-in annotations.
1. Strong Password
@StrongPassword
private String password;
Checks:
- Uppercase letter
- Number
- Special character
- Minimum length
2. Company Email
@CompanyEmail
private String email;
Only accepts:
john@company.com
Rejects:
john@gmail.com
3. Adult Validation
@Adult(minAge = 18)
private LocalDate dob;
Ensures the user’s age is at least 18.
4. Employee ID
@EmployeeId
private String employeeId;
Valid examples:
EMP12345
EMP98765
Invalid examples:
123EMP
emp12345
5. Phone Number
@ValidPhoneNumber
private String phone;
Checks country-specific phone number formats.
6. Password Confirmation
@PasswordMatches
public class RegisterRequest {
private String password;
private String confirmPassword;
}
Validation:
password.equals(confirmPassword)
7. File Type Validation
@ValidFileType
private MultipartFile resume;
Allows only:
- DOCX
Rejects:
- EXE
- ZIP
8. File Size Validation
@MaxFileSize(5)
private MultipartFile file;
Ensures uploaded files are under the allowed size.
9. Future Date Validation
@FutureWithinDays(30)
private LocalDate appointmentDate;
Ensures the selected date falls within the next 30 days.
10. Price Validation
@ValidPrice
private Product product;
Checks rules such as:
Discount Price < Original Price
When Should You Create a Custom Validator?
Create one when:
- Business rules are complex.
- Validation logic is reused across multiple classes.
- Built-in annotations are not expressive enough.
- You want cleaner, more readable code.
Avoid creating custom validators for simple validations already handled by built-in annotations like @NotNull, @Email, or @Size.
Benefits of Custom Validators
- Improves readability
- Encourages code reuse
- Centralizes business rules
- Makes maintenance easier
- Reduces duplicate validation logic
- Keeps DTOs and entities clean
Summary
Custom validators are a powerful feature of Jakarta Bean Validation that allow developers to encapsulate complex business validation logic into reusable annotations.
A custom validator consists of:
- A custom annotation (e.g.,
@StrongPassword) - A validator class implementing
ConstraintValidator
Whenever Spring encounters the custom annotation during validation, it automatically invokes the associated validator’s isValid() method. This approach keeps your code clean, readable, reusable, and aligned with business requirements.
As your applications grow, custom validators become an essential tool for enforcing domain-specific rules while maintaining a clean and maintainable codebase.