I'm trying to implement some custom validation logic for a Spring Boot endpoint using a combination of JSR-303 Bean Validation API
and Spring's Validator
.
Based on the Validator
class diagram it appears to be possible to extend one of CustomValidatorBean
, SpringValidatorAdapter
or LocalValidatorFactoryBean
to add some custom validation logic into an overridden method validate(Object target, Errors errors)
.
However, if I create a validator extending any of these three classes and register it using @InitBinder
its validate(Object target, Errors errors)
method is never invoked and no validation is performed. If I remove @InitBinder
then a default Spring validator performs the JSR-303 Bean Validation
.
REST controller:
@RestController
public class PersonEndpoint {
@InitBinder("person")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new PersonValidator());
}
@RequestMapping(path = "/person", method = RequestMethod.PUT)
public ResponseEntity<Person> add(@Valid @RequestBody Person person) {
person = personService.save(person);
return ResponseEntity.ok().body(person);
}
}
Custom validator:
public class PersonValidator extends CustomValidatorBean {
@Override
public boolean supports(Class<?> clazz) {
return Person.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
super.validate(target, errors);
System.out.println("PersonValidator.validate() target="+ target +" errors="+ errors);
}
}
If my validator implements org.springframework.validation.Validator
then its validate(Object target, Errors errors)
method is called but JSR-303 Bean Validation is not performed prior to it. I can implement my custom JSR-303 validation similar to the way SpringValidatorAdapter
implements its JSR-303 Bean Validation, but there has to be a way to extend it instead:
@Override
public void validate(Object target, Errors errors) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validate(target), errors);
}
}
I have looked at using custom JSR-303 constraints to avoid using org.springframework.validation.Validator
all together but there must be a way to make a custom validator work.
Spring validation documentation is not super clear on combining the two:
An application can also register additional Spring Validator instances per DataBinder instance, as described in Section 9.8.3, “Configuring a DataBinder”. This may be useful for plugging in validation logic without the use of annotations.
And then later on it touches on configuring multiple Validator
instances:
A DataBinder can also be configured with multiple Validator instances via dataBinder.addValidators and dataBinder.replaceValidators. This is useful when combining globally configured Bean Validation with a Spring Validator configured locally on a DataBinder instance. See ???.
I'm using Spring Boot version 1.4.0.
Validator
and useaddValidators
instead ofsetValidator
. This will invoke both the JSR-303 (default configured validator) and your custom one. However you are probably better of using JSR-303 validator/constraint instead of using 2 different mechanisms. – NonalcoholicaddValidators
instead ofsetValidator
did the trick. I also agree that using JSR-303, @AssertTrue method based specifically for cross fields validation, is probably a cleaner solution. A code example is available at github.com/pavelfomin/spring-boot-rest-example/tree/feature/…. In the example, the middle name validation is performed via custom spring validator while last name validation is handled by the default jsr 303 validator. – Burtis