Custom validator in Spring – example annotation

Although built-in validation support in Spring is largely sufficient for standard use cases, sooner or later you will run into a situation when the sets of validation annotations provided by JSR 303 or Hibernate Validator aren’t enough. In this post you will learn how to create a simple constraint annotation served by a custom validator with access to the Spring context of a Spring Boot application.

Presented samples works both in Spring Boot 1.x and 2.x

1. Setup of study case

For demonstration purpose, let’s consider a REST endpoint which will allow registration for new users of our application. Our goal of this tutorial is to validate login uniqueness provided by a client of the service.

public class UserController {

    private UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;

    public void register(@RequestBody @Valid User user) {;


In a real application we would create some persistent storage for the collection of our users, but for simplicity of the example we’re going to use an in-memory storage directly in the repository class. In addition, we implement a method which based on the given login looks for a user in the registered user collection. We’re going to use it later on in our custom validator.

class UserRepository {

    private List<User> registeredUsers = new LinkedList<>();

    void save(User user) {

    Optional<User> findByLogin(String login) {
                .filter(user -> user.getLogin().equals(login))


Finally, we declare the user data model which will act as the input for our service. The login field is annotated with a @UniqueLogin annotation which we’re going to create in the next step.

public class User {

    private String login;
    private char[] password;

    private User() {
        // no-arg Jackson constructor

    public User(String login, char[] password) {
        this.login = login;
        this.password = password;
    // getters omitted

Note: By default Jackson uses reflection to set values of fields and requires no-argument constructor to be declared for a class. You can make the constructor private to maintain the interface of your class unpolluted for public use and to keep Jackson working correctly.

2. Custom contraint annotation

The declaration of @UniqueLogin may look quite complex at the first glance, but don’t get discouraged. You can find the explanation under the code listing.

@Target({ElementType.METHOD, ElementType.FIELD})
@Constraint(validatedBy = UniqueLoginValidator.class)
public @interface UniqueLogin {
    String message() default "{}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

Our custom annotation is itself annotated with three other annotations. If you have ever created a custom annotation, @Target and @Retention shouldn’t be new to you. If not, they are responsible for describing where the annotation can be applied and whether or not it should be available in the byte code so it might be read reflectively. In our case we allow using @UniqueLogin for class fields and methods because constrains can be declared on fields and setters.

More interesting is the @Constraint annotation, which actually marks our annotation for use as a validation constraint. In the validatedBy attribute, we should declare the class that will contain actual validation logic. We’re going to implement it in the next paragraph.

All attributes of our custom annotation are required by the @Constraint annotation. If you’re interested in their purpose, I refer you to the JavaDoc. The groups and payload attributes are left blank to fulfill the contract. In the message attribute we specified the key of the message that will be returned when the validator finds an error.

3. Validation error message

By default, error message keys are being searched in the file called that should be available on the application class path. If you don’t have one already in your app, it’s a good moment to create it and add the following key into its content. given login is already in use

4. Custom validator implementation

Now it’s time for actual verification logic and the class that we declared in the validatedBy attribute of our custom constraint annotation.

class UniqueLoginValidator implements ConstraintValidator<UniqueLogin, String> {

    private UserRepository userRepository;

    public UniqueLoginValidator(UserRepository userRepository) {
        this.userRepository = userRepository;

    public void initialize(UniqueLogin constraint) {

    public boolean isValid(String login, ConstraintValidatorContext context) {
        return login != null && !userRepository.findByLogin(login).isPresent();


The first thing that you should notice is the fact that the class isn’t annotated with any Spring component marker, yet it has a dependency on the UserRepository class which is managed by our Spring context. The Spring framework automatically detects all classes which implement the  ConstraintValidator interface, instantiate them, and wire all dependencies (Note this demo uses Spring 4.3, hence @Autowired annotation isn’t required by the constructor).

The ConstraintValidator interface expects two generic types. The first one is the corresponding constraint annotation. The second is the type of field which will be the target of this validator, since our login field is declared as String, we placed here this type.

The interface requires two methods to be implemented. The name of the initialize() method is self-explanatory, in our simple case we leave it. The isValid() method is the place where verification logic should be placed. Here, without any problem, we make use of the injected UserRepository instance.

5. Unit testing

Finally, we can test our solution. Because our validator relies upon the Spring Context, the integration test is mandatory.

public class UserTest {

    private UserRepository userRepository;
    private Validator validator;

    public void shouldValidateDuplicatedLogin() throws Exception {
        // given
        String login = "daniel";
        User predefinedUser = new User(login, "pass".toCharArray());;
        // when
        User newUser = new User(login, "asd".toCharArray());
        Set<ConstraintViolation<User>> violations = validator.validate(newUser);
        // then
        assertEquals(1, violations.size());


The test is pretty straightforward. First, we populate the registered user collection in the repository with a predefined user. Next, we use the Validator abstraction injected from the Spring context to verify whether another user object with the same login as the predefined one is valid. As the result, we expect to receive one violation of constrains and that is what happens after test execution.

6. Summary

In this topic, we learned that in order to create a custom field verification, we need to create two elements. We start with a marker annotation similar to widely known constraint annotations like @NotNull or @Size that we can use in data model classes. The annotation doesn’t verify anything itself, hence we have to implement a corresponding constraint validator class. The Spring framework takes care of the rest by registering the class and injecting all (if any) declared dependencies.

As usual, the whole sample application can be found in the GitHub repository. You can also continue reading the validation series and learn about parametrized constraints. If you have any questions or doubts, please leave a comment or send me a private message.


Articles you may like