Validation & Exception Handling
Custom Validators — Writing Your Own Validation Logic
Implementing ConstraintValidator and custom annotations.
Introduction
While Jakarta Validation provides many built-in constraints, applications often require complex business validation rules. Spring allows you to define custom annotations linked to a validator class implementing the ConstraintValidator interface. These custom validators run seamlessly as part of the standard validation lifecycle and benefit from Spring's dependency injection.
Why It Matters
Writing validation logic explicitly inside controllers or services pollutes business logic and leads to duplication. Custom validators centralize validation, making it reusable across different DTOs and API endpoints, while keeping the domain models cleanly documented.
Real-World Analogy
Consider custom validators like custom filters or checks on assembly lines. The manufacturer uses standard screens for weight and dimensions (built-in validation), but needs a specialized sensor to verify a customized part number pattern (custom validation). Once created, the specialized sensor fits directly into the main inspection line alongside the default filters.
How It Works
Creating a custom validator requires two components:
- A custom annotation marked with
@Constraint(validatedBy = ValidatorClass.class). - A validator class implementing
ConstraintValidator<Annotation, Type>.
Because Hibernate Validator integration integrates with Spring, the validator class is initialized as a Spring Bean, allowing you to inject repositories, services, or properties (using @Autowired or constructor injection) directly into the validator to run database or external checks.
Practical Example
Let's build a custom validator to verify if a field value matches one of a set of allowed values (e.g. valid order statuses: "PENDING", "APPROVED", "DELIVERED"):
1. Create the Custom Constraint Annotation
2. Implement the ConstraintValidator Class
3. Apply the Custom Annotation on a DTO
Quick Quiz
Q1: Which interface must custom validators implement to link with a validation annotation?
A) CustomValidator
B) ConstraintValidator<A, T>
C) ValidationRule<T>
D) JakartaConstraint
Answer: B — ConstraintValidator<A, T> binds the custom annotation type and the target data type.
Q2: Should a custom validator return false if the input value is null?
A) Yes, always
B) No, it should usually return true and let @NotNull handle null checks separately
C) Null values automatically bypass validation entirely and can't be customized
D) Validators never receive null values
Answer: B — Best practice is to return true for null so that constraints are clean, decoupled, and combinable.
Scenario-Based Challenge
Production Scenario:
You are creating a custom validator @UniqueEmail that needs to query the database using a UserRepository to verify that an email is not registered yet. Can you inject the repository bean into your validator, and are there any thread-safety issues to watch out for?
Yes, Spring automatically hooks into the validation factory, allowing standard DI. You can annotate your validator with @Component (optional in modern boot versions as Hibernate integrates DI by default) and inject UserRepository using standard constructor injection. Since validator instances are usually managed as singletons or thread-safe instances by the validator provider, keep the validator stateless (avoid keeping request-specific variables in class instance fields).
Interview Questions
1. How can you customize the error message dynamically inside a ConstraintValidator?
By default, the message in the annotation is returned. To change it dynamically, disable default messaging via context.disableDefaultConstraintViolation(), and call context.buildConstraintViolationWithTemplate("Your custom message here").addConstraintViolation() inside the validator.
2. Can you apply a custom validator at the class level instead of field level?
Yes, by defining ElementType.TYPE in the annotation's @Target list. Class-level validation is useful for cross-field validation rules (e.g. checking that a password and confirmPassword fields match or startDate is before endDate).
Production Considerations
Keep database queries within custom validators optimized (e.g., using indexes). Since validation runs on every request, slow validator database operations can degrade API throughput significantly. For high-volume endpoints, consider caching validations or moving validation check logic to service layer layers where transaction profiles are controlled.