19

I am totally new to Spring and I have looked in to a few answers on SO for the asked problem. Here are the links:

Spring 3.1 Autowiring does not work inside custom constraint validator

Autowiring a service into a validator

Autowired Repository is Null in Custom Constraint Validator

I have a Spring project in which I want to use Hibernate Validator for an object validation. Based on what I read online and a few forums I tried to inject validator as follows:

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

But wherever I was using

@Autowired
Validator validator;

It was taking Spring's validator implementation instead of the Hibernate's validator. I couldn't figure out how to exactly inject Hibernate validator and simply Autowire it across other classes so I used a cheap trick, now my Java Config looks like this

@Bean
      public Validator validator() {
        // ValidatorImpl is Hibernate's implementation of the Validator
        return new ValidatorImpl();
      }

(I would really appreciate if someone can actually point me into the right direction on how to avoid getting Hibernate Validator in this Hacky way)

But lets come to the main issue here:

Here is custom validation definition

@Target( { METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER } )
@Retention(RUNTIME)
@Constraint(validatedBy = EmployeeValidator.class)
@Documented
public @interface EmployeeValidation {
String message() default "{constraints.employeeConstraints}";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
}

My Custom Validator

public class EmployeeValidator implements ConstraintValidator<EmployeeValidation , Object> {

@Autowired
private EmployeeService employeeService;

@Override
public void initialize(EmployeeValidation constraintAnnotation) {
//do Something
 }

@Override
public boolean isValid(String type, ConstraintValidatorContext context) {
    return false;
}
}

In the above Custom Constraint Validator I get the employeeService null. I know that any implementations of ConstraintValidator are not instantiated when Spring is starting up but I thought adding the ValidatorImpl() will actually fix that. But it didn't.

Now I am stuck with a really hacky workaround and I do not want to continue with a code like that. Can someone please help me with my situation.

P.S. These are my imports in the Java Config file:

import org.hibernate.validator.HibernateValidator; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.context.MessageSource; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.ComponentScan; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.context.annotation.PropertySource; 
import org.springframework.context.support.ReloadableResourceBundleMessageSource; 
import org.springframework.core.env.Environment; 
import org.springframework.validation.Validator; 
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; 
import org.springframework.web.servlet.LocaleResolver; 
import org.springframework.web.servlet.ViewResolver; 
import org.springframework.web.servlet.config.annotation.EnableWebMvc; 
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; 
import org.springframework.web.servlet.i18n.CookieLocaleResolver; 
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; 
import org.springframework.web.servlet.view.InternalResourceViewResolver; 
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Nick Div
  • 5,338
  • 12
  • 65
  • 127
  • do you want hibernate validator or not? how does ValidatorImpl look like? – brain storm Jun 22 '16 at 05:03
  • @brainstorm Yes I want the Hibernate Validator i.e. ValidatorImpl -- that's what Hibernate's Implementation of Validator is called. I am not sure what do you mean by "how does ValidatorImpl" look like. Its a concrete class that implements the interface Validator and abides by its blue-print. – Nick Div Jun 22 '16 at 05:07
  • can you show your import statements? is it spring-boot app? – brain storm Jun 22 '16 at 05:08
  • @brainstorm I just want to get the Hibernate Validator without actually hard-coding it into my Java Config. So basically wherever I use the following code: {@Autowired Validator validator;} I want the implementation to be Hibernate's implementation of the Validator interface, not Spring's – Nick Div Jun 22 '16 at 05:09
  • @brainstorm No it is not Spring-boot app. From which class do you want me to post the import statements? I can edit the question. – Nick Div Jun 22 '16 at 05:10
  • imports from java config or appconfig..where ever you have defined your validator beans? – brain storm Jun 22 '16 at 05:15
  • @brainstorm I've added it in the question. – Nick Div Jun 22 '16 at 05:19
  • You are just getting a simple wrapper around the hibernate validator, so your hack doesn't make it any better only harder to understand for someone who understands spring. `@Autowired` will not work for constraint validators for the simple reason that spring simply doesn't control the lifecycle of them. The are created as needed by the JSR-303 validator implementation and not by Spring and as such `@Autowired` doesn't have any effect. – M. Deinum Jun 22 '16 at 05:54
  • Also there are patches in the framework which make auto wiring work, just show HOW you are using the validator? Basically your workarounds make things worse and makes me think you are using it in a way it is not intended to be used. – M. Deinum Jun 22 '16 at 06:01

7 Answers7

27

I hope the solution will help someone:

@Bean
public Validator validator () {

    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure().constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
        .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();

    return validator;
}

Initializing the validator with SpringConstraintValidatorFactory so that injection works and providing the validator implementation to be Hibernate.class works in the following manner:

  1. Your objects will be validated by the library of your choice
  2. Your custom validators will be able to use Spring's functionality while having validation to be executed by Hibernate.

How it works: Hibernate's ConstraintValidatorFactory does not initialize any ConstraintValidators unless they are called but SpringConstraintValidatorFactory does by giving AutowireCapableBeanFactory to it.

EDIT

As mentioned in one of the comments by @shabyasaschi To inject autowireCapableBeanFactory you can change the method signature as:

Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) {

or add getter and setter for it in the config file as follows:

public AutowireCapableBeanFactory getAutowireCapableBeanFactory() {
        return autowireCapableBeanFactory;
}

public void setAutowireCapableBeanFactory(AutowireCapableBeanFactory autowireCapableBeanFactory) {
        this.autowireCapableBeanFactory = autowireCapableBeanFactory;
}
adnauseam
  • 627
  • 6
  • 14
Nick Div
  • 5,338
  • 12
  • 65
  • 127
  • 1
    Thanks. It works! However, the method signature should be for autowiring the dependency: `public Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) {` – shabyasachi Nov 21 '16 at 11:19
  • @shabyasachi I am glad it helped you :) This was like a brain teaser!! – Nick Div May 01 '17 at 07:19
  • @NickDiv what's the parameter autowireCapableBeanFactory? From where should I take that? Thanks – drenda Oct 07 '17 at 10:57
  • It didn't work for me :/ ... can you post full code of yours ? – Mssm Jun 26 '19 at 23:34
  • @Mouley I do not have that project anymore but if you can open a new question describing what exactly is happening or any errors that you see, me and other SO members can take a look – Nick Div Jun 27 '19 at 05:19
  • @Nick Div I opened a question here => https://stackoverflow.com/questions/56782292/configuring-spring-validation-and-hibernate-validation/56784106#56784106 – Mssm Jun 27 '19 at 15:57
  • @Mouley I see that you already found a solution. That's great. – Nick Div Jun 28 '19 at 05:23
  • @NickDiv yeah but unfortunatly this causes another problem... https://stackoverflow.com/questions/56798777/bean-constraint-validation-causes-stackoverflow ... looks like findBy is validating again and again resulting to a stackoverflowexception – Mssm Jun 29 '19 at 12:25
  • Check this option http://dolszewski.com/spring/custom-validation-annotation-in-spring/ This works fine so no need for anything special. Maybe some new Spring change that was not available when this question was posted. – codesalsa Jan 03 '20 at 21:46
  • This solution did not work with Spring Boot 2. The repository gets autowired everywhere else except in the ContraintValidator of a Custom Validator invoked manually. – ganaraj May 12 '20 at 12:32
  • @ganaraj I am guessing this was for an older spring version. I believe the link in the previous comment might help you. – Nick Div May 12 '20 at 14:46
  • @CoderJammer I am glad I was able to help :) – Nick Div Aug 08 '20 at 01:03
  • @ganaraj Hi, I would really appreciate if you could share if you find any solution. It drives me crazy!!! – dushkin Nov 05 '20 at 21:11
  • @NickDiv Thanks Nick for the solution. I am a Spring novice. I don't understand where and how should I use validator bean? Where should I put it? Should I explicitly call it? Thanks! – dushkin Nov 05 '20 at 22:23
  • @dushkin You can place/inject the Validator Bean inside your Custom Constraint Validators just like how you Autowire dependencies in your other beans. For e.g. a repository is Autowired into a service bean – Nick Div Nov 06 '20 at 15:30
  • @dushkin I never found a solution for this. Worked around this by defining my constraint under a group and calling the Group Validator before persisting. (Set> violations = validator.validate(user);) – ganaraj Dec 31 '20 at 09:03
4

You can fix this with two aproaches:

  • Try to inject Services on your validator using Spring.

  • Initialize it manually overriding Validator's initialize method.

I had the same problem time ago and finally i decided to use second option avoiding tons of problems.

As you point you must define one initialize method on your validator and there you can use a ServiceUtils to get the service bean you need:

@Autowired
private EmployeeService employeeService;

@Override
public void initialize(EmployeeValidation constraintAnnotation) {
  //Use an utility service to get Spring beans
  employeeService = ServiceUtils.getEmployeeService();
}

And ServiceUtils is a normal Spring bean with a static reference to itself used in the static methods.

@Component
public class ServiceUtils {
  private static ServiceUtils instance;

  @Autowired
  private EmployeeService employeeService;

  /* Post constructor */

  @PostConstruct
  public void fillInstance() {
    instance = this;
  }

  /*static methods */

  public static EmployeeService getEmployeeService) {
    return instance.employeeService;
  }
}

So you are using Spring to inject the services you need but not in the usual way. Hope this helps.

Ricardo Vila
  • 1,626
  • 1
  • 18
  • 34
  • 2
    I appreciate the effort and I am sure this would definitely work but again this is a hacky way of doing things that can be done in a cleaner way. I have seen the @Autowired working for people (On forums) so I know that there is definitely some way to do it .Just need to figure it out. Again, thanks for the try. – Nick Div Jun 22 '16 at 12:28
  • Well it's a little hacky but not a bad way to get Spring beans inside the validators. Please, if you find the solution, would you mind to update this question? As i told i couldn't get it work using simply @autowired, thanks. – Ricardo Vila Jun 22 '16 at 13:16
  • Causes a Stackoverflow Exception when this is used to autowire a JpaRepository to a ContraintValidator – ganaraj May 12 '20 at 11:16
2

In your bean definition

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

What's the type of Validator in the method definition? You should make sure it returns javax.validation.Validator, not Validator from Spring.

Letting Spring bootstrap the validator will it also cause to pass a SpringConstraintValidatorFactory to Hibernate Validator which will enable dependency injection within constraint validators.

Gunnar
  • 18,095
  • 1
  • 53
  • 73
  • I have only one import in my config file for Validator, I also tried javax.validation.Validator explicitly in the return type. – Nick Div Jun 22 '16 at 12:25
  • Have you tried debugging it? In `LocalValidatorFactoryBean` you should step through the code passing Spring's constraint validator factory to the bootstrapped validator. – Gunnar Jun 23 '16 at 06:29
  • But it was not using Hibernate's validator in the first place. And when I explicitly set Hibernate's validator from the Java config it was taking Hibernate's ConstraintValidatorFactoryImpl (which is obvious) I have debugged and tried more options than I have posted here. I did a find a solution though which I have posted as an answer. I do appreciate your attempt to help a fellow programmer. Thanks. – Nick Div Jun 23 '16 at 14:55
  • Hum, ok; Still odd, `LocalValidatorFactoryBean` should be using `SpringConstraintValidatorFactory` by default. – Gunnar Jun 23 '16 at 15:42
  • I think there is some mis-communication here. LocalValidatorFactoryBean does use SpringConstraintValidatorFactory. Problem was that I was not able to make LocalValidatorFactryBean return me the implementation of HIbernate so I manually plugged it in from the Config but doing that gave me ConstraintValidatorFactoryImpl as my ConstraintValidator provider which again caused issues for me. So then I decided to override that as well. So here is what I am doing, I am asking for Hibernate validator by default which uses Constraintfactory of Spring. – Nick Div Jun 23 '16 at 19:37
1

There is nothing wrong with your code It depends how are you creating your ValidatorFactory. Create a bean and let Spring handle it.

@Bean
public ValidatorFactory validatorFactory(){
    return Validation.buildDefaultValidatorFactory();
}
Kumar Shanoo
  • 125
  • 4
0

Think about it. There should have been no issue with using the @Autowired inside a Constraint validator class. This means that something is wrong.

This issue has been reported on various platform and I have not seen a good solution. I have seen some workaround though.

Here is what I found. You may notice that the validation is happening twice. the first time it should work but the second time you got a null related error message. The problem should be that the entity or the class that you is being validated is being used twice in your controller. For example, you may want validate the entity class and try to save it at the same time in the same method in the controller method. when you try to save the entity, it will try to validate the object again and this time the @Autowired object will be null.

Here is what you can do for this scenario

You can use dto to carry the validation annotation and copy the property of the dto class to your entity class before you save it into the database. your scenario may be different but the solution approach should be the same.

Below is an illustration of code that works

public ResponseEntity<InstitutionModel> create(@Valid @RequestBody InstitutionDto institutiondto) {
    Institution institution = new Institution();
    BeanUtils.copyProperties(institutiondto, institution);
    return Optional.of(this.institutionService.save(institution)).map(institutionModelAssembler::toModel)
            .map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
glouis
  • 1
  • 3
-1
private XXXService xxxService = SpringContextHolder.getBean(XXXService.class);
  • Congratulations on your first post! While this code snippet may solve the question, [including an explanation](//meta.stackexchange.com/questions/114762) really helps to improve the quality of your post. Remember that you are also answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – robinCTS Jan 04 '18 at 07:44
-4

Thats worked for me. For guys who search at now

public class EmployeeValidator implements ConstraintValidator<EmployeeValidation , Object> {


    private EmployeeService employeeService;

    public EmployeeValidator(EmployeeService employeeService){
        this.employeeService = employeeService;
    }

    ...
}
ykembayev
  • 108
  • 1
  • 8
  • 7
    No idea how that worked for you, but here it gives me "Caused by: java.lang.NoSuchMethodException: training.edit.jpa.validator.CompanyValidator.()" The has to be a default constructor it appears. – user3235738 Jul 31 '19 at 17:24