What type of exception to throw in Spring RestController when validation fails? What type of exception to throw in Spring RestController when validation fails? spring spring

What type of exception to throw in Spring RestController when validation fails?


To me the simplest way looks like validating the object with an errors object, and use it in a MethodArgumentNotValidException.

@RequestMapping(method = RequestMethod.POST)public ResponseEntity<?> createOrder(@RequestBody @Validated(InputChecks.class) Order order)                throws NoSuchMethodException, SecurityException, MethodArgumentNotValidException {    // Some processing of the Order goes here    SpringValidatorAdapter v = new SpringValidatorAdapter(validator);    BeanPropertyBindingResult errors = new BeanPropertyBindingResult(order, "order");    v.validate(order, errors, FinalChecks.class);    if (errors.hasErrors()) {        throw new MethodArgumentNotValidException(                new MethodParameter(this.getClass().getDeclaredMethod("createOrder", Order.class), 0),                errors);    }    orders.put(order);    HttpHeaders headers = new HttpHeaders();    headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());    return new ResponseEntity<>(null, headers, HttpStatus.CREATED);}

This way the errors found during the second validation step have exactly the same structure as the errors found during the input validation on the @validated parameters.


For handling validation errors in the second run, i can think of three different approaches. First, you can extract validation error messages from Set of ConstraintViolations and then return an appropriate HTTP response, say 400 Bad Request, with validation error messages as the response body:

Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);if (!violations.isEmpty()) {    Set<String> validationMessages = violations                                     .stream()                                     .map(ConstraintViolation::getMessage)                                     .collect(Collectors.toSet());    return ResponseEntity.badRequest().body(validationMessages);}// the happy path

This approach is suitable for situations when the double validation is a requirement for a few controllers. Otherwise, it's better to throw a brand new Exception or reuse spring related exceptions, say MethodArgumentNotValidException, and define a ControllerAdvice that handle them universally:

Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);if (!violations.isEmpty()) {    throw new ValidationException(violations);}

And the controller advice:

@ControllerAdvicepublic class ValidationControllerAdvice {    @ExceptionHandler(ValidationException.class)    public ResponseEntity handleValidtionErrors(ValidationException ex) {        return ResponseEntity.badRequest().body(ex.getViolations().stream()...);    }}

You can also throw one of spring exceptions like MethodArgumentNotValidException. In order to do so, you need to convert the Set of ConstraintViolations to an instance of BindingResult and pass it to the MethodArgumentNotValidException's constructor.