Skip to content

Improve method validation support for containers with constraints on container elements  #31887

@mcso

Description

@mcso

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.

  1. 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:
image
List parameter:
image
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.

  1. 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

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions