When creating a REST controller with Spring Boot it's possible to use Bean Validation to validate the methods arguments annotating the class with @Validated
.
Then Bean Validation annotations can be used directly on the argument or, if the argument is a complex class with properties that must be validated (such as the request body), using @Valid
.
The inconsistency I found was while handling violation on those validations.
While using the annotations from javax.validation.constraints
(the validations from the Bean Validation API) the violations are handled by the @Validated
handler (MethodValidationInterceptor
) and throws ConstraintViolationException
.
When using @Valid
the violation is handled by ModelAttributeMethodProcessor
and throws MethodArgumentNotValidException
.
See the problem?
The @Validated
, a Spring annotation that says it is a "Variant of JSR-303's Valid" and "Designed for convenient use with Spring's JSR-303 support but not JSR-303 specific" (see it here), throws ConstraintViolationException
, an exception from the Bean Validation API (JSR-303).
And the opposite also is true, the @Valid
(from Bean Validation API) throws MethodArgumentNotValidException
(Spring exception).
I think it would be more concise if their behavior changed between them.
Does it makes sense?
Comment From: philwebb
I've transferred this issue to the spring-framework
team since the code in question is part of spring-webmvc
.
Comment From: ah1508
There is also the BindException
, thrown by spring mvc if the invalid object has been built from the request parameters.
exemple : GET /books?title=&author.lastname=
calls a method which declares a @Valid Book
parameter (query by example),where title
property is @NotBlank
.
This forces to handle BindException
and MethodNotValidArgumentException
, but the code is the same. For example :
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> onMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return e.getBindingResult().getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
}
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> onBindException(BindException e) {
return e.getBindingResult().getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
}
a @ValidationFailureHandler
could help to unify these common behaviors.
Comment From: sbrannen
@ah1508,
This forces to handle
BindException
andMethodNotValidArgumentException
, but the code is the same. For example :
I was going to say you can handle both exceptions at the same time as follows...
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> onValidationFailureException(Exception ex) {
return ((CommonBindingResultInterface) ex).getBindingResult().getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
}
... but then I realized that MethodArgumentNotValidException
and BindException
do not share a common super type that declares the getBindingResult()
method.
So one option to make this easier would be to introduce a common interface (like the CommonBindingResultInterface
in the above example) that declares BindingResult getBindingResult()
, and then MethodArgumentNotValidException
and BindException
could both implement that interface.
Comment From: rstoyanchev
@rafafael03 I've read your description several times and I can't be sure I understand what you mean and there are claims that aren't true.
First you can use either @Valid
or @Validated
interchangeably. They result in the same handling and do not raise different exceptions. The main difference for Spring MVC applications is that @Validated
allows you to specify validation groups (hints). Also ModelAttributeMethodProcessor
does not and throw MethodArgumentNotValidException
but BindException
.
What @ah1508 has written is correct. Validation through either of these annotations is supported for @ModelAttribute
and for @RequestBody
arguments. The former raises BindException
but only if you don't handle it in the controller method by declaring a BindingResult
argument. The latter raises MethodArgumentNotValidException
.
Is this the same as your point?
As for finding a way to consolidate, perhaps MethodArgumentNotValidException
could be made to extend BindException
. They both have the same internal state more or less.
Comment From: ah1508
Indeed, if MethodArgumentNotValidException
extends BindException
, only one @ExceptionHandler
would be needed.
Comment From: sbrannen
Tentatively slated for 5.3 for pending design work and assessment of viability regarding the proposal to have MethodArgumentNotValidException
extend BindException
.