Closed
Description
Affects: 5.3.1
Relates to #23107
Basically, I have the exact same feeling as the author did but he never clarified his point.
You can validate a DTO using @Valid
annotation (whether it is built from a bunch of query parameters or from the body) but you can't validate literals like that. So, to validate a request parameter or a path parameter, one has to apply @Validated
annotation on the controller class level to enable the AOP proxy for method validation, then it will pick up any JSR 380 annotations.
So, here is a little example. Given the following definition:
class UserDto { @Email String email; }
@RestController
@RequestMapping("users")
@Validated
class UserController {
@PostMapping
void createUser(@Valid @RequestBody UserDto userDto) {...}
@GetMapping("{email}")
UserDto getUser(@Email @PathVariable String email) {...}
If we send the following requests, we will get these results:
--------->
POST users
{
"email": "invalid-email"
}
<---------
throws MethodArgumentNotValidException
--------->
GET users/invalid-email
<---------
throws ConstraintViolationException
Now, handling these 2 is not very nice although we want the same result in the end:
class ValidationErrorResponse { List<Violation> violations; }
class Violation { String fieldName, message; }
@ExceptionHandler
ResponseEntity<ValidationErrorResponse> onConstraintValidationException(ConstraintViolationException e) {
ValidationErrorResponse error = new ValidationErrorResponse();
for (ConstraintViolation violation : e.getConstraintViolations()) {
error.getViolations().add(
new Violation(violation.getPropertyPath().toString(), violation.getMessage()));
}
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler
ResponseEntity<ValidationErrorResponse> onMethodArgumentNotValidException(MethodArgumentNotValidException e) {
ValidationErrorResponse error = new ValidationErrorResponse();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
error.getViolations().add(
new Violation(fieldError.getField(), fieldError.getDefaultMessage()));
}
return ResponseEntity.badRequest().body(error);
}
So, I see 3 inconveniences:
- I have to always specify @Valid annotation whenever I have a @RequestBody. Can't we just assume I want to validate all bodies? I feel like I might forget it in one place and it won't be validated. Or maybe we could add @Valid onto the DTO class itself and look there for it? Or maybe we could just scan all fields and see if any field has the @constraint annotation on it?
- I have to always specify @validated annotation on the controller class level to inspect the literals. Why doesn't it inspect them by default since it already searches for @Valid/@validated on the body?
- Error handling is cumbersome