Skip to main content
Advertisement

6.3 Custom Validator Implementation

The basic annotations provided by default (like @NotBlank, @Min, etc.) may not be enough to validate all complex business rules of a service. For example, conditions like "the start date must be before the end date" or "specific words must not be included" are solved by creating custom annotations and Validators.

1. Creating a Custom Annotation

First, use @interface to create your own validation annotation. Let's take an example of a @StrongPassword annotation that checks if a password contains certain special characters.

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = StrongPasswordValidator.class) // Specify the class to perform the validation logic
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface StrongPassword {

String message() default "Password must contain at least one uppercase letter, lowercase letter, number, and special character.";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

An annotation for Bean Validation must have three essential properties: message, groups, and payload.

2. Implementing Validator Logic

Implement the StrongPasswordValidator format specified with validatedBy above. It must implement the ConstraintValidator interface.

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {

@Override
public void initialize(StrongPassword constraintAnnotation) {
// Write initialization logic if needed
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isBlank()) {
return false;
}

// Strong password validation logic using regular expressions
boolean hasUppercase = value.matches(".*[A-Z].*");
boolean hasLowercase = value.matches(".*[a-z].*");
boolean hasNumber = value.matches(".*\\d.*");
boolean hasSpecialChar = value.matches(".*[!@#$%^&*()].*");

return hasUppercase && hasLowercase && hasNumber && hasSpecialChar;
}
}

3. Applying the Validator

Now you can naturally attach the created annotation to a DTO field to use it.

public class UserRegisterDto {
@NotBlank
private String username;

@StrongPassword
private String password;
}

By utilizing custom Validators in this manner, you can neatly add flexible and reusable domain-specific logic while maintaining the core validation features provided by the framework.

Advertisement