When I started to use Spring Boot I had some issues with the validation. The @Validated annotation triggered the validation as requested but the validation terminated with an org.springframework.validation.BindException
instead of filling the BindingResult
object.
My initial method looked similar to this one:
@PostMapping("/do-something")
@PreAuthorize("hasRole('ADMIN')")
public String update(Model model,
@ModelAttribute @Validated RequestDAO requestData,
@CurrentUser User user,
BindingResult bindingResult) {
[...]
}
To resolve this issue I used the debugger to see what happens inside...
/* org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument */
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
/* org.springframework.web.method.annotation.ModelAttributeMethodProcessor#isBindExceptionRequired */
/**
* Whether to raise a fatal bind exception on validation errors.
* @param parameter the method parameter declaration
* @return {@code true} if the next method parameter is not of type {@link Errors}
* @since 5.0
*/
protected boolean isBindExceptionRequired(MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
Therefore the correct order of the method arguments is very important. The BindingResult
has to follow the @Validated
attribute, otherwise an org.springframework.validation.BindException
is thrown.
So the working code looks like this:
@PostMapping("/do-something")
@PreAuthorize("hasRole('ADMIN')")
public String update(Model model,
@ModelAttribute @Validated RequestDAO requestData,
BindingResult bindingResult, /* BindingResult must follow @Validated */
@CurrentUser User user) {
[...]
}