ReviseAlgo Logo

Validation & Exception Handling

Custom Validators — Writing Your Own Validation Logic

Implementing ConstraintValidator and custom annotations.

Last Updated: June 14, 2026 12 min read

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:

  1. A custom annotation marked with @Constraint(validatedBy = ValidatorClass.class).
  2. 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?

View Solution

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.