19

I have the following code in a Spring controller:

@Autowired
private javax.validation.Validator validator;

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form) {
    Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
    ...
}

Is it possible to map errors to Spring's BindingResult object without manually going through all the errors and adding them to the BindingResult? Something like this:

// NOTE: this is imaginary code
BindingResult bindingResult = BindingResult.fromConstraintViolations(errors);

I know it is possible to annotate the CustomForm parameter with @Valid and let Spring inject BindingResult as another method's parameter, but it's not an option in my case.

// I know this is possible, but doesn't work for me
public String submitForm(@Valid CustomForm form, BindingResult bindingResult) {
    ...
}
M. Justin
  • 14,487
  • 7
  • 91
  • 130
Anton Moiseev
  • 2,834
  • 4
  • 24
  • 30

5 Answers5

11

A simpler approach could be to use Spring's abstraction org.springframework.validation.Validator instead, you can get hold of a validator by having this bean in the context:

<bean id="jsr303Validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />

@Autowired @Qualifier("jsr303Validator") Validator validator;

With this abstraction in place, you can use the validator this way, passing in your bindingResult:

validator.validate(obj, bindingResult);
Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125
  • Thanks! That's definitely more concise. – Anton Moiseev Feb 15 '13 at 11:35
  • 3
    I don't see method validate which accepts BindingResult – gstackoverflow Apr 13 '15 at 23:04
  • 2
    If you don't see the validate method that accepts the binding results, it means your Validator validator is a javax.validation.Validator. Change it to a org.springframework.validation.Validator and you'll be fine. No need to change the bean definition. – Icegras Dec 08 '16 at 15:52
5

Spring uses a SpringValidatorAdapter to convert javax.validation.ConstraintViolation objects to ObjectError or FieldError objects, as found in the binding result. The BindStatus then uses a message source (like the web application context itself) to translate the errors. In short, you could do:

SpringValidatorAdapter springValidator = new SpringValidatorAdapter(validator);
BindingResult bindingResult= new BeanPropertyBindingResult(myBeanToValidate, "myBeanName");
springValidator.validate(myBeanToValidate, bindingResult);

This is easier when writing a unit test, because you don't even need to create a Spring context.

Kristiaan
  • 396
  • 3
  • 7
1

Expanding on Kristiaan's answer, for testing purposes it is not necessary to create a spring context to validate using Spring's bindingResult. The following is an example:

public class ValidatorTest {

    javax.validation.Validator javaxValidator = Validation.buildDefaultValidatorFactory().getValidator();
    org.springframework.validation.Validator springValidator = new SpringValidatorAdapter(javaxValidator);

    @Test
    public void anExampleTest() {

    JSR303AnnotatedClassToTest   ctt  = new JSR303AnnotatedClassToTest( ..init vars..)

    ... test setup...

    WebDataBinder dataBinder = new WebDataBinder(ctt);
    dataBinder.setValidator(springValidator);
    dataBinder.validate();
    BindingResult bindingResult = dataBinder.getBindingResult(); 

    ... test analysis ...

    }
}

This approach doesn't require creating a binding result ahead of time, the dataBinder builds the right one for you.

sdw
  • 623
  • 9
  • 10
1
@RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form) {
    Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);

    BindingResult bindingResult = toBindingResult(errors, form, "form");
    ...
}

private BindingResult toBindingResult(ConstraintViolationException e, Object object, String objectName) {
    BindingResult bindingResult = new BeanPropertyBindingResult(object, objectName);
    new AddConstraintViolationsToErrors().addConstraintViolations(e.getConstraintViolations(), bindingResult);
    return bindingResult;
}

private static class AddConstraintViolationsToErrors extends SpringValidatorAdapter {
    public AddConstraintViolationsToErrors() {
        super(Validation.buildDefaultValidatorFactory().getValidator()); // Validator is not actually used
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public void addConstraintViolations(Set<? super ConstraintViolation<?>> violations, Errors errors) {
        // Using raw type since processConstraintViolations specifically expects ConstraintViolation<Object>
        super.processConstraintViolations((Set) violations, errors);
    }
}

Unlike the other answers to this question, this solution handles the case where there already exists a Set<ConstraintViolation<?>> which needs to be converted to to a BindingResult.

Explanation

Spring provides the SpringValidatorAdapter class to perform bean validations, storing the results in an Errors instance (note that BindingResult extends Errors). The normal manual use of this class would be to use it to perform the validations via the validate method:

Validator beanValidator = Validation.buildDefaultValidatorFactory().getValidator();
SpringValidatorAdapter validatorAdapter = new SpringValidatorAdapter(beanValidator);

BindException bindException = new BindException(form, "form");
validatorAdapter.validate(form, bindException);

However, this doesn't help in the case where there already exists a Set<ConstraintViolation<?>> which needs to be converted to a BindingResult.

It is still possible to achieve this goal, though it does require jumping through a couple extra hoops. SpringValidatorAdapter contains a processConstraintViolations method which converts the ConstraintViolation objects into the appropriate Spring ObjectError subtypes, and stores them on an Errors object. However, this method is protected, limiting its accesibility to subclasses.

This limitation can be worked around by creating a custom subclass of SpringValidatorAdapter which delegates to or exposes the protected method. It is not a typical usage, but it works.

public class AddConstraintViolationsToErrors extends SpringValidatorAdapter {
    public AddConstraintViolationsToErrors() {
        super(Validation.buildDefaultValidatorFactory().getValidator()); // Validator is not actually used
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public void addConstraintViolations(Set<? super ConstraintViolation<?>> violations, Errors errors) {
        // Using raw type since processConstraintViolations specifically expects ConstraintViolation<Object>
        super.processConstraintViolations((Set) violations, errors);
    }
}

This custom class can be used to populate a newly created BindingResult, achieving the goal of creating a BindingResult from a Set<ConstraintViolation<?>>.

private BindingResult toBindException(ConstraintViolationException e, Object object, String objectName) {
    BindingResult bindingResult = new BeanPropertyBindingResult(object, objectName);
    new AddConstraintViolationsToErrors().addConstraintViolations(e.getConstraintViolations(), bindingResult);
    return bindingResult;
}
M. Justin
  • 14,487
  • 7
  • 91
  • 130
0

I've encountered a similar issue and this is how I resolved it.

Given your example, this is how I implemented it

First, I used a smart validator, and in the method I let spring inject the BindingResult

@Autowired
private org.springframework.validation.SmartValidator validator;

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form, BindingResult bindingResult) {
    Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
    ...
}

And then using that binding result i pass it in the SmartValidator so that any errors will be bounded to BindingResult.

validator.validate(form, bindingResult);
if(bindingResult.hasErrors()) {
     throw new BindException(bindingResult);
}
Ruelos Joel
  • 2,209
  • 3
  • 19
  • 33