Spring – Dependency Injection in JSR-303 Constraint Validator with Spring fails

dependency-injectionspringvalidation

I have the same problem as here and here but couldn't find a solution yet.

So my sample test project will show the whole relevant configuration and code:

Constraint annotation:

@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FooValidator.class)
public @interface FooValid {

    String message();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Annotated PoJo:

public class Foo {

    @FooValid(message = "Test failed")
    private Integer test;
    [...]
}

Annotated Service with @Validated:

@Service
@Validated
public class FooService {

    private final Test test;

    @Autowired
    public FooService(final Test test) {
        this.test = test;
    }

    public void foo(@Valid final Foo foo) {
        this.test.test(foo);
    }
}

JSR-303 ConstraintValidator:

public class FooValidator implements ConstraintValidator<FooValid, Integer> {

    @Autowired
    private ValidationService validationService;

    @Override
    public void initialize(final FooValid constraintAnnotation) {
        // TODO Auto-generated method stub

    }

    @Override
    public boolean isValid(final Integer value, final ConstraintValidatorContext context) {
        // this.validationService is always NULL!
        Assert.notNull(this.validationService, "the validationService must not be null");
        return false;
    }

}

Injected ValidationService:

@Service
public class ValidationService {

    public void test(final Foo foo) {
        System.out.println(foo);
    }
}

Spring boot application and configuration:

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(final String[] args) {
        final ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        final FooService service = context.getBean(FooService.class);
        service.foo(new Foo());
    }

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

    @Bean
    public static MethodValidationPostProcessor validationPostProcessor() {
        return new MethodValidationPostProcessor();
    }

}

relevant maven pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.1.9.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>
</dependencies>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <start-class>demo.Application</start-class>
    <java.version>1.7</java.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

I'm using the LocalValidatorFactoryBean with the default SpringConstraintValidatorFactory.
But why the dependency injection is not working in the ConstraintValidator and the ValidationService could not be autowired?

By the way if I don't use @Validated at the service, inject in opposite the spring or javax Validator interface and call manually "validator.validate" the dependency injection will work. But I don't want to call the validate method in every service manually.

Many thanks for help 🙂

Best Answer

I have fought the same problem in Spring Boot environment and I found out that Hibernate internal implementation got in instead of the configured Spring's one. When the application started, debugger caught a line with the Spring's factory but later in runtime there was Hibernate's one. After some debugging, I came to the conclusion that MethodValidationPostProcessor got the internal one. Therefore I configured it as follows:

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

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
    MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
    methodValidationPostProcessor.setValidator(validator);
    return methodValidationPostProcessor;
}

Note the setter for validator - it did the job.