Home > other >  Spring Boot: RestControllerAdvice: handle wrapped exceptions
Spring Boot: RestControllerAdvice: handle wrapped exceptions

Time:01-25

Let's say we have @RestControllerAdvice-annotated class like this:

@RestControllerAdvice
public class RestResponseExceptionHandler {

  @ExceptionHandler(MyBusinessException .class)
  public ResponseEntity<ErrorResponse> handleMyBusinessException (MyBusinessException ex) {
      return createResponseEntity(ex, ex.getErrorCode());
  }

  @ExceptionHandler({IllegalArgumentException.class, ValidationException.class, DataIntegrityViolationException.class})
  public ResponseEntity<ErrorResponse> handleInvalidPropertyException(RuntimeException ex) {
      return createResponseEntity(ex, ErrorCode.DATA_INVALID);
  }

  [...]

  @ExceptionHandler(RuntimeException.class)
  public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) {
        return createResponseEntity(ex, ErrorCode.UNKNOWN);
  }

  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleException(Exception ex) {
      return createResponseEntity(ex, ErrorCode.UNKNOWN);
  }

  @ExceptionHandler(WrapperException .class)
  public ResponseEntity<ErrorResponse> handleWrapperException (WrapperException ex) {
      Exception exception = ex.getWrappedException();
      // re-dispatch exception here
  }
}

For a known WrapperException, is it possible to somehow re-dispatch the wrapped exception?

I tried several things, e.g. rethrowing the wrapped excption or explicitly call a custom method of our ErrorController and re-throw the exception there, but so far no luck.

CodePudding user response:

Why would you want to re-throw which may create unnecessary branching. You can do a conditional check and call the appropriate exception handler method as below.

@ExceptionHandler(WrapperException .class)
public ResponseEntity<ErrorResponse> handleWrapperException (WrapperException ex) {
  Exception exception = ex.getWrappedException();
  if (exception instanceof MyBusinessException) {
      return handleMyBusinessException((MyBusinessException) exception);
  }
  return //Default
 } 

CodePudding user response:

I think i figured the spring-ish way to do it. You can autowire in your RestResponseExceptionHandler bean of this type - HandlerExceptionResolver. Spring autoconfigures few of those for you, i managed to make it work with this one - handlerExceptionResolver. Something like this:

@ControllerAdvice
public class ErrorHandler {

    private final HandlerExceptionResolver resolver;

    @Autowired
    public ErrorHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
        this.resolver = resolver;
    }

    //that's the wrapped exception handler from your case
    @ExceptionHandler(Exception.class)
    public void exception(Exception exception, HttpServletRequest request, HttpServletResponse response) {
        //get the wrapped exception
        Exception wrappedException = (Exception) exception.getCause();
        //this will dispatch the handling to the handler for the wrapped exception
        this.resolver.resolveException(request, response, null, wrappedException);
    }

    @ExceptionHandler(IndexOutOfBoundsException.class)
    public ResponseEntity<Object> indexOutOfBoundsException() {
        return ResponseEntity.of(Optional.of("IndexOutOfBoundsException"));
    }

    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<Object> runtimeException() {
        return ResponseEntity.of(Optional.of("NullPointerException"));
    }

    @ExceptionHandler(IOException.class)
    public ResponseEntity<Object> ioException() {
        return ResponseEntity.of(Optional.of("IOException"));
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Object> illegalArgumentException() {
        return ResponseEntity.of(Optional.of("IllegalArgumentException"));
    }
}

You will have to use the request and response in the wrapped exception handler, otherwise won't be able to handle it. When you call resolveException() with the wrapped one, it will reroute it to the correct handler for the wrapped exception. I did some testing using a controller to throw exceptions like this one, and everything was resolved correctly.

@Controller
public class ExcController {

    @GetMapping("/thr")
    public String throwExc() throws Exception {
        throw new Exception(new NullPointerException());
    }

    @GetMapping("/thr2")
    public String throwExc2() throws Exception {
        throw new IOException();
    }
}

CodePudding user response:

As of Spring 5.3, @ExceptionHandler already looks into the cause exceptions when trying to find a match. So if you are on a recent Spring version, you can just remove your @ExceptionHandler(WrapperException.class) method, and it should just work as you expect.

See https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.html

  •  Tags:  
  • Related