Close

Spring - Mixed validation using JSR 349 Annotations and Spring validator

[Last Updated: Dec 22, 2023]

Using JSR 349/380 annotations is the recommended way to perform bean validations. Although it's not always possible to use it for complex validation.

Let's say in our previous examples, Order object has an additional field, customerId, which has to be validated against the ids loaded from a database. In those kind of scenarios pure JSR 349 annotation based approach is not very suitable.

Spring provides enough flexibility to mix JSR 349/380 annotation with Spring's org.springframework.validation.Validator approaches.

Example

The object to be validated

package com.logicbig.example;

import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Date;

public class Order {
  @NotNull(message = "{date.empty}")
  @Future(message = "{date.future}")
  private Date date;

  @NotNull(message = "{price.empty}")
  @DecimalMin(value = "0", inclusive = false, message = "{price.invalid}")
  private BigDecimal price;

  String customerId;
    .............
}

Implementing Spring Validator interface

package com.logicbig.example;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

public class OrderValidator implements Validator {

  @Override
  public boolean supports(Class<?> clazz) {
      return Order.class == clazz;
  }

  @Override
  public void validate(Object target, Errors errors) {
      ValidationUtils.rejectIfEmpty(errors, "customerId", "customerId.empty");

      Order order = (Order) target;
      if (order.getCustomerId() != null) {
          Customer customer = getCustomerById(order.getCustomerId());
          if (customer == null) {
              errors.reject("customer.id.invalid",
                      new Object[]{order.getCustomerId()},
                      "Customer id is not valid");
          }
      }
  }

  private Customer getCustomerById(String customerId) {
      //just for test returning null.
      // in real example the customer could be loop up in database etc.
      return null;
  }
}

A generic approach to loop up validator and apply validation

package com.logicbig.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;
import jakarta.validation.ConstraintViolation;
import java.awt.print.Book;
import java.util.Comparator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

public class GenericValidator {

  @Autowired
  ApplicationContext context;

  public boolean validateObject(Object objectToValidate) {

      Map<String, Validator> validatorMap = context.getBeansOfType(Validator.class);
      if (validatorMap == null) {
          return true;
      }

      DataBinder binder = new DataBinder(objectToValidate);

      //in this example two validators are register OrderValidator
      // and LocalValidatorFactoryBean which will do JSR 349 validations.
      for (Validator validator : validatorMap.values()) {

          if (validator.supports(objectToValidate.getClass())) {
              binder.addValidators(validator);
          }
      }

      binder.validate();
      BindingResult bindingResult = binder.getBindingResult();
      if (bindingResult.hasErrors()) {
          ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
          messageSource.setBasename("ValidationMessages");

          System.err.println(messageSource.getMessage("object.invalid",
                  new Object[]{objectToValidate.getClass().getSimpleName()}, Locale.US));

          bindingResult.getAllErrors()
                       .stream()
                       .map(e -> messageSource.getMessage(e, Locale.US))
                       .sorted(Comparator.naturalOrder())
                       .forEach(System.err::println);
          return false;
      }

      return true;
  }
}

Validating Order object

package com.logicbig.example;

import org.springframework.beans.factory.annotation.Autowired;

public class ClientBean {

  @Autowired
  private GenericValidator genericValidator;

  public void processOrder(Order order) {
      if (genericValidator.validateObject(order)) {
          System.out.println("processing " + order);
      }
  }
}

Spring Configuration

package com.logicbig.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class Config {

  @Bean
  public ClientBean clientBean() {
      return new ClientBean();
  }

  @Bean
  public Validator validatorFactory() {
      return new LocalValidatorFactoryBean();
  }

  @Bean
  public OrderValidator orderValidator() {
      return new OrderValidator();
  }

  @Bean
  public GenericValidator genericValidator() {
      return new GenericValidator();
  }
}

Main class

package com.logicbig.example;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ValidationMixedExample {

  public static void main(String[] args) {

      AnnotationConfigApplicationContext context = new
              AnnotationConfigApplicationContext(Config.class);

      ClientBean clientBean = context.getBean(ClientBean.class);
      Order order = new Order();
      //  order.setPrice(BigDecimal.TEN);
      //  order.setDate(new Date(System.currentTimeMillis() + 100000));
      order.setCustomerId("111");
      clientBean.processOrder(order);
  }
}

Output

Order has validation errors:
Date cannot be empty
No customer found with id 111
Price cannot be empty

Example Project

Dependencies and Technologies Used:

  • spring-context 6.1.2 (Spring Context)
     Version Compatibility: 3.2.3.RELEASE - 6.1.2Version List
    ×

    Version compatibilities of spring-context with this example:

    • 3.2.3.RELEASE
    • 3.2.4.RELEASE
    • 3.2.5.RELEASE
    • 3.2.6.RELEASE
    • 3.2.7.RELEASE
    • 3.2.8.RELEASE
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • Compatible Java Version: 17+
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.1.0
    • 6.1.1
    • 6.1.2

    Versions in green have been tested.

  • hibernate-validator 8.0.1.Final (Hibernate's Jakarta Bean Validation reference implementation)
  • expressly 5.0.0 (Jakarta Expression Language Implementation)
  • JDK 17
  • Maven 3.8.1

Mixed validation using JSR 349 Annotations and Spring validator Select All Download
  • spring-mixing-jsr349-annotations-and-spring-validator
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • GenericValidator.java
          • resources

    See Also