Customizing Bean Validation in Spring

Here’s an example of how to customize the javax.validation.Validator instance in Spring 5.  Validator is the Java API for bean validation that was introduced in the JSR-303 specification.  Hibernate Validator is bundled with Spring Boot 2, and is the de facto reference implementation for bean validation.

Why would you need to do something like this?  In this case, I wanted to configure temporal validation tolerance, which provides an error margin when validating annotations like @Past and @Future, and as of Hibernate Validator 6.0 is a global configuration setting.  This tolerance can help if system clocks aren’t perfectly in sync, for example.

There are several different ways to accomplish this, but I wanted to stick with a Java based Spring configuration approach.  I also wanted a default ClockProvider implementation that could be overridden in a unit test using @MockBean, in order to test HibernateValidatorConfiguration.TEMPORAL_VALIDATION_TOLERANCE.

Conveniently, Spring 5 has a LocalValidatorFactoryBean#postProcessConfiguration() method that makes this easy. Here’s an example:

@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
    return new LocalValidatorFactoryBean() {
        @Override
        protected void postProcessConfiguration(Configuration<?> configuration) {
            configuration.clockProvider(clockProvider());
            configuration.addProperty(HibernateValidatorConfiguration.TEMPORAL_VALIDATION_TOLERANCE,
                    String.valueOf(temporalValidationTolerance));
        }
    };
}

@Bean
public ClockProvider clockProvider() {
    return Clock::systemUTC;
}

In tests, the ClockProvider can be mocked to return a fixed time clock:

// For testing the clock, return a fixed time.
when(clockProvider.getClock()).thenReturn(new Clock() {
    @Override
    public ZoneId getZone() {
        return ZoneId.of("UTC");
    }

    @Override
    public Clock withZone(ZoneId zone) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Instant instant() {
        return fixedTime;
    }
});

A full Spring Boot demo application using Gradle can be found here.