- Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
Affects: 6.1.1
With the method validation improvement in 6.1, a few inconsistencies compared to bean validation. I could imagine there are usecases I am not aware of being the reason, but it seems like the two types could be a lot more aligned.
- List naming
@GetMapping public String get(@RequestParam(required = false) @Valid List<@NotBlank String> list) { // ... }
Validation of the above snippet will result in the object name when handling the HandlerMethodValidationException
to be "stringList". Had the parameter been a plain String, you can get the name of the parameter as expected. With bean validation, it also manages to tell the correct field path, name, and index.
In addition, the error for the invalid list when using HandlerMethodValidationException.getAllErrors()
doesn't contain information about which index had the validation failure. You can reconstruct this information by using HandlerMethodValidationException.getAllValidationResults()
instead, as the object there does contain index information.
As a sidenote. When looking at the codes
field when handling the error, it seems to be very different for a String parameter and a list.
String parameter:
List parameter:
Controller and parameter name is not present there either, generally making it look like lists are not handled in a similar way that the String parameter is.
- Error handling
@Override protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { List<Map<String, String>> errors = ex .getAllErrors() .stream() .map(objectError -> { Map<String, String> error = new HashMap<>(); error.put("field", ((FieldError) objectError).getField()); error.put("error", objectError.getDefaultMessage()); return error; }).toList(); ex .getBody() .setProperty("errors", errors); return super.handleMethodArgumentNotValid(ex, headers, status, request); } @Override protected ResponseEntity<Object> handleHandlerMethodValidationException(HandlerMethodValidationException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { List<Map<String, String>> errors = ex .getAllErrors() .stream() .map(messageSourceResolvable -> { Map<String, String> error = new HashMap<>(); String parameterValue; if(messageSourceResolvable instanceof ObjectError objectError) { parameterValue = objectError.getObjectName(); } else { parameterValue = ((MessageSourceResolvable) messageSourceResolvable.getArguments()[0]).getDefaultMessage(); } error.put("parameter", parameterValue); error.put("error", messageSourceResolvable.getDefaultMessage()); return error; }).toList(); ex .getBody() .setProperty("errors", errors); return super.handleHandlerMethodValidationException(ex, headers, status, request); }
As a continuation of the inconsistency above, when handling the exception afterwards, extracting the validation failure information is inconsistent.
For bean validation, it seems like the errors are always type FieldError
(really SpringValidationAdapter#ViolationFieldError
, but that is private), making it consistent across the different field types in your beans. MethodArgumentNotValidException.getAllErrors() is returning List<ObjectError>
.
Method validation is less consistent. HandlerMethodValidationException.getAllErrors()
return List<? extends MessageSourceResolvable>, and the specific type of MessageSourceResolvable
differs depending if it is a String or a list parameter. For a list, it is ObjectError
(SpringValidationAdapter#ViolationObjectError
), and for String it is DefaultMessageSourceResolvable
, making you have to consider the specific type if you want consistent error information. If you want the field name:
String: ((MessageSourceResolvable) messageSourceResolvable.getArguments()[0]).getDefaultMessage()
List: ((ObjectError) messageSourceResolvable).getObjectName()
This can also be seen in the sidenote from point 1, the object name is not in the same place in either of the examples.
Demo project: demo.zip