Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you retain Spring's Built-In REST Response JSON body with a @ControllerAdvise (and @RestControllerAdvise) Class?

In Spring 4.x, if you use a @RestControllerAdvise (or @ControllerAdvice) that extends ResponseEntityExceptionHandler, the default exception handling with nice and informative JSON response bodies, is no longer returned by default for arguments marked as @Valid.

How do you get the default JSON bodies to be returned while using a ResponseEntityExceptionHandler based @RestControllerAdvice?

The following is a simple, yet full example describing this question. Using these classes:

@RestController
class CarsController {

  @PostMapping("/cars")
  public void createCar(@RequestBody @Valid Car car) {
    System.out.println("Creating " + car);
    throw new WhateverException();
  }

  @ExceptionHandler(WhateverException.class)
  @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
  public void handleWhateverException(){
    System.out.println("Handling a WhateverException.");
  }

}

class Car {

  @NotNull
  private String make;

  @NotNull
  private String model;

  ...getter/setters omitted for brevity...
}

class WhateverException extends RuntimeException {}

If you submit a POST to /cars with

{
    "make": "BMW"
}

It responds with a 400 and the following body:

{
  "timestamp": 1491020374642,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.MethodArgumentNotValidException",
  "errors": [
    {
      "codes": [
        "NotNull.car.model",
        "NotNull.model",
        "NotNull.java.lang.String",
        "NotNull"
      ],
      "arguments": [
        {
          "codes": [
            "car.model",
            "model"
          ],
          "arguments": null,
          "defaultMessage": "model",
          "code": "model"
        }
      ],
      "defaultMessage": "may not be null",
      "objectName": "car",
      "field": "model",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotNull"
    }
  ],
  "message": "Validation failed for object='car'. Error count: 1",
  "path": "/cars"
}

However if you move the exception handling method to it's own class marked @RestControllerAdvice, which extends from ResponseEntityExceptionHandler such as the following:

@RestControllerAdvice
class RestExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(WhateverException.class)
  @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
  public void handleWhateverException(WhateverException e, HttpServletRequest httpServletRequest) {
    System.out.println("Handling a WhateverException.");
  }

}

You'll get a 400 with an empty body, which is caused by ResponseEntityExceptionHandler providing a method (handleMethodArgumentNotValid(..)), which builds a response where the body is null.

How would you alter this @RestControllerAdvice class to trigger the original handling that occurs, which provides a JSON body describing why the submitted request is invalid?

like image 418
peterl Avatar asked Apr 01 '17 05:04

peterl


1 Answers

How about this?

   @RestControllerAdvice
    class RestExceptionHandler {

      @ExceptionHandler(org.springframework.validation.BindException.class)
      public ResponseEntity<String> handleBindException(org.springframework.validation.BindException e) {
         return ResponseEntity.badRequest().body(e.getMessage());
      }
   }

when @Valid fails, it throws BindException. Which you can handle it like this. or you can just do throw e which would give you the exact same response as was thrown earlier

like image 200
pvpkiran Avatar answered Oct 13 '22 20:10

pvpkiran