javaintermediate

Spring Boot — Custom Validator Annotation

Create custom validation annotations with ConstraintValidator for domain-specific field validation.

java
import jakarta.validation.*;
import java.lang.annotation.*;
import java.util.regex.Pattern;

// 1. Define custom annotation
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface ValidPhone {
    String message() default "Invalid phone number format";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 2. Implement validator
public class PhoneNumberValidator implements ConstraintValidator<ValidPhone, String> {
    private static final Pattern PHONE_PATTERN =
        Pattern.compile("^\\+?[1-9]\\d{1,14}$"); // E.164 format

    @Override
    public boolean isValid(String value, ConstraintValidatorContext ctx) {
        if (value == null) return true; // use @NotNull separately
        return PHONE_PATTERN.matcher(value).matches();
    }
}

// 3. Cross-field validation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordMatchValidator.class)
public @interface PasswordMatch {
    String message() default "Passwords do not match";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, RegisterRequest> {
    @Override
    public boolean isValid(RegisterRequest req, ConstraintValidatorContext ctx) {
        if (req.password() == null) return true;
        return req.password().equals(req.confirmPassword());
    }
}

// 4. Usage in DTO
@PasswordMatch
record RegisterRequest(
    @NotBlank String name,
    @Email String email,
    @ValidPhone String phone,
    @Size(min = 8) String password,
    String confirmPassword
) {}

Use Cases

  • Domain-specific input validation
  • Cross-field validation for forms
  • Reusable validation logic across DTOs

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.