Tuesday, 2 February, 2021
Validation in Spring Boot applications
Validation in Spring Boot applications can be done in many different ways. Depending on your requirements some ways might fit better to your application than others. In this post we will explore the usual options to validate data in Spring Boot applications.
Validation is done by using the Bean Validation API. The reference implementation for the Bean Validation API is Hibernate Validator.
All required dependencies are packaged in the Spring Boot starter POM spring-boot-starter-validation. So usually all you need to get started is the following dependency:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Validation constraints are defined by annotating fields with appropriate Bean Validation annotations. For example:
public class Address { @NotBlank @Size(max = 50) private String street; @NotBlank @Size(max = 50) private String city; @NotBlank @Size(max = 10) private String zipCode; @NotBlank @Size(max = 3) private String countryCOde; // getters + setters }
I think these annotations are quite self-explanatory. We will use this Address class in many of the following examples.
You can find a complete list of build in constraint annotations in the Bean Validation documentation. Of course you can also define you own validation constraints by creating a custom ConstraintValidator.
Defining validation constraints is only one part. Next we need to trigger the actual validation. This can be done by Spring or by manually invoking a Validator. We will see both approaches in the next sections.
Validating incoming request data
When building a REST API with Spring Boot it is likely you want to validate incoming request data. This can be done by simply adding the @Valid Annotation to the @RequestBody method parameter. For example:
@RestController public class AddressController { @PostMapping("/address") public void createAddress(@Valid @RequestBody Address address) { // .. } }
Spring now automatically validates the passed Address object based on the previously defined constraints.
This type of validation is usually used to make sure the data sent by the client is syntactically correct. If the validation fails the controller method is not called and a HTTP 400 (Bad request) response is returned to the client. More complex business specific validation constraints should typically be checked later in the business layer.
Persistence layer validation
When using a relational database in your Spring Boot application, it is likely that you are also using Spring Data and Hibernate. Hibernate comes with supports for Bean Validation. If your entities contain Bean Validation annotations, those are automatically checked when persisting an entity.
Note that the persistence layer should definitely not be the only location for validation. If validation fails here, it usually means that some sort of validation is missing in other application components. Persistence layer validation should be seen as the last line of defense. In addition to that, the persistence layer is usually too late for business related validation.
Method parameter validation
Another option is the method parameter validation provided by Spring. This allows us to add Bean Validation annotations to method parameters. Spring then uses an AOP interceptor to validate the parameters before the actual method is called.
For example:
@Service @Validated public class CustomerService { public void updateAddress( @Pattern(regexp = "\\w{2}\\d{8}") String customerId, @Valid Address newAddress ) { // .. } }
This approach can be useful to validate data coming into your service layer. However, before committing to this approach you should be aware of its limitations as this type of validation only works if Spring proxies are involved. See my separate post about Method parameter validation for more details.
Note that this approach can make unit testing harder. In order to test validation constraints in your services you now have to bootstrap a Spring application context.
Triggering Bean Validation programmatically
In the previous validation solutions the actual validation is triggered by Spring or Hibernate. However, it can be quite viable to trigger validation manually. This gives us great flexibility in integrating validation into the appropriate location of our application.
We start by creating a ValidationFacade bean:
@Component public class ValidationFacade { private final Validator validator; public ValidationFacade(Validator validator) { this.validator = validator; } public <T> void validate(T object, Class<?>... groups) { Set<ConstraintViolation<T>> violations = validator.validate(object, groups); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } } }
This bean accepts a Validator as constructor parameter. Validator is part of the Bean Validation API and responsible for validating Java objects. An instance of Validator is automatically provided by Spring, so it can be injected into our ValidationFacade.
Within the validate(..) method we use the Validator to validate a passed object. The result is a Set of ConstraintViolations. If no validation constraints are violated (= the object is valid) the Set is empty. Otherwise, we throw a ConstraintViolationException.
We can now inject our ValidationFacade into other beans. For example:
@Service public class CustomerService { private final ValidationFacade validationFacade; public CustomerService(ValidationFacade validationFacade) { this.validationFacade = validationFacade; } public void updateAddress(String customerId, Address newAddress) { validationFacade.validate(newAddress); // ... } }
To validate an object (here newAddress) we simply have to call the validate(..) method of ValidationFacade. Of course we could also inject the Validator directly in our CustomerService. However, in case of validation errors we usually do not want to deal with the returned Set of ConstraintViolations. Instead it is likely we simply want to throw an exception, which is exactly what ValidationFacade is doing.
Often this is a good approach for validation in the service/business layer. It is not limited to method parameters and can be used with different types of objects. For example, we can load an object from the database, modify it and then validate it before we continue.
This way is also quite good to unit test as we can simply mock ValidationFacade. In case we want real validation in unit tests, the required Validator instance can be created manually (as shown in the next section). Both cases do not require to bootstrap a Spring application context in our tests.
Validating inside business classes
Another approach is to move validation inside your actual business classes. When doing Domain Driven Design this can be a good fit. For example, when creating an Address instance the constructor can make sure we are not able to construct an invalid object:
public class Address { @NotBlank @Size(max = 50) private String street; @NotBlank @Size(max = 50) private String city; ... public Address(String street, String city) { this.street = street; this.city = city; ValidationHelper.validate(this); } }
Here the constructor calls a static validate(..) method to validate the object state. This static validate(..) methods looks similar to the previously shown method in ValidationFacade:
public class ValidationHelper { private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); public static <T> void validate(T object, Class<?>... groups) { Set<ConstraintViolation<T>> violations = validator.validate(object, groups); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } } }
The difference here is that we do not retrieve the Validator instance by Spring. Instead, we create it manually by using:
Validation.buildDefaultValidatorFactory().getValidator()
This way we can integrate validation directly into domain objects without relying on someone outside to validate the object.
Summary
We saw different ways to deal with validation in Spring Boot applications. Validating incoming request data is good to reject nonsense as early as possible. Persistence layer validation should only be used as additional layer of safety. Method validation can be quite useful, but make sure you understand the limitations. Even if triggering Bean Validation programmatically takes a bit more effort, it is usually the most flexible way.
You can find the source code for the shown examples on GitHub.