9

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 :)

Community
  • 1
  • 1
oil ID
  • 113
  • 1
  • 1
  • 5

4 Answers4

11

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.

Michael
  • 1,453
  • 3
  • 20
  • 28
  • I just tested it one minute ago, and it just works with `return new MethodValidationPostProcessor ()`. – Benjamin M Jan 27 '15 at 19:34
  • Of course, why didn't I think of this in the first place. – davo Nov 18 '15 at 22:19
  • This really does the trick, when using MethodValidationPostProcessor. Unfortunately this isn't mentioned in the documentation, but if you think about it, it just makes sens. I would, hovewer, have the Validator from spring context injected into the methodValidationPostProcessor, in order to have the same instance of the validator, instead of calling the validator() method. I'll edit your answer. – martin Jan 26 '17 at 10:22
  • is it possible to replace default implementation (Hibernate vs Spring) just for some methods / classes? – Bogdan Aug 21 '20 at 14:42
0

I had the same issue. The problem is arises because Hibernate is finding and applying the validators instead of Spring. So you need to set validation mode to NONE when configuring Hibernate:

@Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
    localContainerEntityManagerFactoryBean(DataSource dataSource) {
    LocalContainerEntityManagerFactoryBean lcemfb =
        new LocalContainerEntityManagerFactoryBean();
    lcemfb.setDataSource(dataSource);
    lcemfb.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    lcemfb.setValidationMode(ValidationMode.NONE);
    // Continue configuration...

If you have confgured a LocalValidationFactoryBean Spring will pick up any validators annotated with @Component and autowire them.

  • Thanks for your response! No, this doesn't work. In my sample project above there is no hibernate *LocalContainerEntityManagerFactoryBean*. Also annotating the validator with *@Component* doesn't help :( Do you have another idea? That's really the whole code above... – oil ID Dec 19 '14 at 19:25
  • 1
    You can disable Hibernate validation in code using a custom `EntityManagerFactory` like I did (see instructions for Spring Boot [here](http://docs.spring.io/spring-boot/docs/current/reference/html/howto-data-access.html)). Or you can disable Hibernate validation by setting `spring.jpa.properties.javax.persistence.validation.mode=none` in `application.properties file` (see instructions [here](http://stackoverflow.com/questions/26764532/how-to-disable-hibernate-validation-in-a-spring-boot-project)). – Philip Healy Dec 20 '14 at 16:31
0

This is what worked for me. I had to used the @Inject tag.

    public class FooValidator implements ConstraintValidator<FooValid, Integer> {
        private ValidationService validationService;
        @Inject
        public FooValidator(ValidationService validationService){
            this.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;
        }

}

gabbon
  • 1
  • 1
0

instead of this just add @Validated at controller class level.

Adriaan
  • 17,741
  • 7
  • 42
  • 75
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 05 '23 at 00:30