Easily configure validators via properties in a Spring Boot project

The annotations from the Java Validation API provide a convenient and flexible tool to ensure the quality of an application’s input. Some of them have parameters that allow for the validation’s fine tuning. The main drawback is that the parameters’ values are resolved at compile time, that’s why they must be static final constants. We’ll show how we can overcome this limitation and configure validators via properties in a Spring Boot project.

It would be great if we could set the parameters’ values through configuration properties. They could be updated easily without the need to redeploy the application, if they were defined in a .properties or .yml file. Unfortunately this is not possible with a standard annotation from the Java Validation API.

An example of such annotation is the @javax.validation.constraints.Size annotation, which allows to set maximum and minimum size for a CharSequence, a Collection, a Map or an array. In the following example we’ll create our own customized version that will retrieve max and min parameters from the application’s properties.

The project

We’ll build upon a project from a previous post. We’re going to create a new custom constraint, apply it to our model class, and update our test suites.

The custom constraint and its validator

First of all we need to create a custom constraint annotation. Its name will be @ConfigurableSize and it will be very similar to the @Size annotation. The main difference is that instead of a max and min parameter of type int we have a minProperty and a maxProperty parameter of type String. Those parameters will default to an empty string, which means that we don’t need to provide a value when using the annotation. Each parameter will hold the name of a configuration property that resolves to an int value.

In our example we’re only providing a validator for CharSequences, but the validators for Collections, Maps and arrays would be very similar. What we want to show is how to implement the configurability via properties.

initialize

The custom validator will retrieve the properties by name and set the min and max values upon initialization. This is what the dependency of type PropertyResolver is for. Notice that the method getRequiredProperty(String, Class) is being used. It will not only retrieve the property value, it will also throw an exception if the property is not resolvable. If the property name is omitted, the min and max will default respectively to 0 and Integer.MAX_VALUE, the same defaults for the @Size constraint. Finally, it will be checked that both min and max are non negative and max is not lesser than min.

isValid

The validation is pretty simple: a null value will be considered valid, then it will be checked that the size of the CharSequence is between min and max. If the validation fails we’ll leverage HibernateĀ“s provider specific implementation of ConstraintValidatorContext to create a constraint violation. Such constraint violation will have the same message as if the violation was on the @Size constraint. Our custom constraint’s behaviour will thus be indistinguishable from the @Size constraint’s behaviour to an external observer.

Testing the validation

Let’s apply our @ConfigurableSize constraint to the fullName field in our Employee class in lieu of the @NotEmpty constraint. Since the field is mandatory, we need to add the @NotNull annotation too.

For the annotation to work, we need to define the employee.fullName.size.min and employee.fullName.size.max properties

employee.fullName.size.min=3
employee.fullName.size.max=128

or, for those who prefer YAML

employee:
  fullName:
    size:
      min: 3
      max: 128

We’ll test the expected properties’ values in our @SpringBootTest class.

To test the validation we need to mock the PropertyResolver dependency. We’ve shown in a previous post how to unit test the validation layer when our custom validators have dependencies.

Let’s create an employee with a full name shorter than 3 characters and an employee with a full name longer than 128 characters. Then we’ll create one test case for each employee.

If we run our tests we’ll see that they are all green. We can now configure the employees’ full name minimum and maximum size via configuration properties!

Conclusion

We showed how to easily configure validators via properties in a Spring Boot project.

It’s possible to leverage the flexibility of Java Validation API’s custom validators and Spring’s dependency injection to configure our constraints via application properties.

We can create custom constraints that behave exactly like the standard constraints, but on top of that offer the advantage of external configurability. We’ll be able to modify the validator’s behaviour just by updating the application’s properties, without the need to modify our code and redeploy our application.

In our example we created a custom constraint called @ConfigurableSize that mimics the @Size constraint. Of course we can do the same for any standard validation constraint whose behaviour varies based on parameter values, like @Pattern, @Digits, @Min, @Max and so on.

The example project is available for download on GitLab.

2 thoughts on “Easily configure validators via properties in a Spring Boot project

  1. Great tutorial. I tried to implement a custom validator based on properties by following your tutorial, unfortunately i get the following error:

    HV000064 Unable to instantiate ConstraintValidator
    Caused by: java.lang.NoSuchMethodException ConfigurableSizeCharSequenceValidator.()

    So I added the no argument constructor and I got a null pointer exception:
    Cannot invoke “org.springframework.core.env.PropertyResolver.getRequiredProperty(String, java.lang.Class)” because “this.propertyResolver” is null

    1. Thank you.

      Did you get the error while running the unit tests? In that case the problem might be in the Validator factory. You need to create a custom Validator factory that will instantiate your custom constraint validators and inject the mocked dependencies in them. The solution is described with more in-depth details in this previous post (which illustrates an earlier version of this tutorial project): https://codemadeclear.com/index.php/2021/01/26/how-to-mock-dependencies-when-unit-testing-custom-validators/#solution

      If you got the error while executing the application (with the whole Spring context running) then it might be some issue with the Spring Boot autoconfiguration. You might want to check if you have all the necessary dependencies in your pom.xml as shown here: https://codemadeclear.com/index.php/2021/01/26/how-to-mock-dependencies-when-unit-testing-custom-validators/#project

      Hope this helps to solve your problem. Let me know.

Comments are closed.