How to Customize the @ConfigurationProperties Validation in Spring
It’s pretty common to use @ConfigurationProperties classes in Spring since they make it very easy to map externalized properties to a Java class. Also, they allow us to validate these properties at startup so we can avoid errors at runtime due to a wrong property configuration. This validation can be easily done just by using the standard JSR-303 annotations. However, sometimes we need to customize the validation to fulfill our requirements.
In this tutorial, we’ll create a @ConfigurationProperties class validated with JSR-303 annotations and after that, we’ll show how to create custom validators for our class.
Setup
For the purpose of this tutorial we’ll create a simple Spring Boot application with the minimal dependencies needed:
We’re using the spring-boot-starter-validation
that includes the hibernate-validator
which is probably the most used JSR-303 implementation.
After that, let’s create a minimal Application class:
This application will be used in the next sections to illustrate our examples.
Creating our @ConfigurationProperties Class
Let’s create the @ConfigurationProperties class that we’re going to validate! This class will simply store some properties to save reports to a file and optionally send them by email:
We’ve added the @Validated annotation to validate the targetFile property with @NotBlank. Note that this is a JSR-303 annotation.
After that, we’ll enable our class in our Application by using the @EnableConfigurationProperties annotation:
At this point, if we try to start our application and we haven’t set the report.targetFile property we get an error:
As see above, the application doesn’t start and it gives a quite explanatory message.
Creating a Custom @ConfigurationProperties Validator
Now that we have our @ConfigurationProperties class ready we’re going to start customizing the validation process. At this moment, we’re only validating the targetFile but we’d also like to validate the emailSubject and recipient properties. However, we want to validate them only if sendByEmail is set to true. To do this we need to create a custom Validator since we can’t do that with annotations.
Let’s implement our custom validator now! To do so we’ll create a ReportConfigValidator class that implements the Spring interface Validator:
As shown, this interface has only 2 methods and they are quite easy to implement:
- boolean supports(Class<?> clazz): it’s used by Spring to find the validators that can validate a specific class. In our case, our Validator only supports our ReportConfig class
- void validate(Object target, Errors errors): it validates the target object. As we can see, we need to cast this object to our class but if the supports method is implemented correctly this cast should be safe. All the failed validations are stored in the errors object that is received as a parameter
Also notice that we’re using the ValidationUtils class that Spring provides to help us with some common validations.
After that, we need to tell Spring to use this class to validate our @ConfigurationProperties classes. Let’s add it to our Application:
Notice that this bean has to be named as configurationPropertiesValidator or Spring won’t find it. It also has to be static since Spring needs to load it very early at the startup of the application.
Finally let’s create our properties and set the sendByEmail property to true:
And let’s try to start the application afterward:
As expected, we’re getting the validation errors that we configured in our custom Validator plus the one we were getting before from the JSR-303 annotations.
Multiple Validators
The example shown in the previous section fits very well the cases where we only have 1 custom validator. Unfortunately, if we need to have more validators we can’t create more than 1 ConfigurationPropertiesValidator bean. One solution could be to use the same validator for all our @ConfigurationProperties classes but it can be a bit tedious if we have many classes. A more convenient solution would be to make our @ConfigurationProperties classes implement the Validator interface themselves.
Let’s do that for our ReportConfig class:
As we can see, it’s the same as we did in our ReportConfigValidator but just inside the @ConfigurationProperties class. If we had more classes to validate we’d also make them implement the Validator interface.
Now we can remove the configurationPropertiesValidator bean from our Application and try to start it again:
As expected, we get the same validation errors.
Accumulating Validators
In our previous example, we had removed the configurationPropertiesValidator since we moved that validator to the @ConfigurationProperties class. But what would happen if we left it? Let’s add it again and run the application:
We can see that now all our validators have been applied. And since 2 of them do the same we got duplicated validation errors.
This happens because Spring looks for validators in 3 different places:
- In the ConfigurationPropertiesValidator bean
- In the classpath if there is a JSR-303 validator and the target class is annotated with @Validated
- In the target class if it implements the Validator interface
All these validators are accumulated when they are present and they all will be applied when a supported target object needs validation. In our latest example, we were using these 3 types of validators. It’s important to keep this in mind when we design our validators not to duplicate or contradict validations.
Conclusion
We’ve just seen how the validation of @ConfigurationProperties classes can be customized in Spring. We’ve presented some scenarios where custom validators are needed and provided a solution for them. We’ve finally explained where Spring looks for validators and how it accumulates them.
The source code of the examples is available at Github.
Leave a comment