Looking for some help with Spring data rest validation regarding proper handling of validation errors:
I\'m so confused with the docs regarding spring-data-rest vali
The answers by @Mathias and @1977 is enough for regular Spring Data REST
calls. However in cases when you need to write custom @RepositoryRestController
s using @RequestBody
and @Valid
JSR-303 annotations didn't work for me.
So, as an addition to the answer, in case of custom @RepositoryRestController
s with @RequestBody
and @Valid
annotation I've added the following @ControllerAdvice
:
/**
* Workaround class for making JSR-303 annotation validation work for controller method parameters.
* Check the issue <a href="https://jira.spring.io/browse/DATAREST-593">DATAREST-593</a>
*/
@ControllerAdvice
public class RequestBodyValidationProcessor extends RequestBodyAdviceAdapter {
private final Validator validator;
public RequestBodyValidationProcessor(@Autowired @Qualifier("mvcValidator") final Validator validator) {
this.validator = validator;
}
@Override
public boolean supports(final MethodParameter methodParameter, final Type targetType, final Class<? extends
HttpMessageConverter<?>> converterType) {
final Annotation[] parameterAnnotations = methodParameter.getParameterAnnotations();
for (final Annotation annotation : parameterAnnotations) {
if (annotation.annotationType().equals(Valid.class)) {
return true;
}
}
return false;
}
@Override
public Object afterBodyRead(final Object body, final HttpInputMessage inputMessage, final MethodParameter
parameter, final Type targetType, final Class<? extends HttpMessageConverter<?>> converterType) {
final Object obj = super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
final BindingResult bindingResult = new BeanPropertyBindingResult(obj, obj.getClass().getCanonicalName());
validator.validate(obj, bindingResult);
if (bindingResult.hasErrors()) {
throw new RuntimeBindException(bindingResult);
}
return obj;
}
}
@Mathias it seems the following is enough for jsr 303 annotations to be checked and for it to auto return a http code of 400 with nice messages (I dont even need BeforeCreateCompanyValidator or BeforeSaveCompanyValidator classes):
@Configuration
public class RestValidationConfiguration extends RepositoryRestConfigurerAdapter{
@Bean
@Primary
/**
* Create a validator to use in bean validation - primary to be able to autowire without qualifier
*/
Validator validator() {
return new LocalValidatorFactoryBean();
}
@Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
Validator validator = validator();
//bean validation always before save and create
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
}
400 response:
{
"errors": [{
"entity": "Company",
"message": "may not be null",
"invalidValue": "null",
"property": "name"
}, {
"entity": "Company",
"message": "may not be null",
"invalidValue": "null",
"property": "address"
}]
}
I think your problem is that the bean validation is happening too late - it is done on the JPA level before persist. I found that - unlike spring mvc - spring-data-rest is not doing bean validation when a controller method is invoked. You will need some extra configuration for this.
You want spring-data-rest to validate your bean - this will give you nice error messages responses and a proper http return code.
I configured my validation in spring-data-rest like this:
@Configuration
public class MySpringDataRestValidationConfiguration extends RepositoryRestConfigurerAdapter {
@Bean
@Primary
/**
* Create a validator to use in bean validation - primary to be able to autowire without qualifier
*/
Validator validator() {
return new LocalValidatorFactoryBean();
}
@Bean
//the bean name starting with beforeCreate will result into registering the validator before insert
public BeforeCreateCompanyValidator beforeCreateCompanyValidator() {
return new BeforeCreateCompanyValidator();
}
@Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
Validator validator = validator();
//bean validation always before save and create
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
}
When bean validation and/or my custom validator find errors I receive a 400 - bad request with a payload like this:
Status = 400
Error message = null
Headers = {Content-Type=[application/hal+json]}
Content type = application/hal+json
Body = {
"errors" : [ {
"entity" : "siteWithAdminUser",
"message" : "may not be null",
"invalidValue" : "null",
"property" : "adminUser"
} ]
}