11

I have a @ControllerAdvice class to handle exceptions from my SpringMVC controllers. I would like to catch an exception of a known type (RuntimeException) in an @ExceptionHandler method then throw the e.getCause() exception and have this exception caught by the same @ControllerAdvice class.

Sample code:

@ControllerAdvice
public class ExceptionHandlingAdvice
{
    @ExceptionHandler( RuntimeException.class )
    private void handleRuntimeException( final RuntimeException e, final HttpServletResponse response ) throws Throwable
    {
        throw e.getCause(); // Can be of many types
    }

    // I want any Exception1 exception thrown by the above handler to be caught in this handler
    @ExceptionHandler( Exception1.class )
    private void handleAnException( final Exception1 e, final HttpServletResponse response ) throws Throwable
    {
        // handle exception
    }
}

Is this possible?

agentgonzo
  • 3,473
  • 3
  • 25
  • 30

2 Answers2

1

You can check if that RuntimeException is instance of Exception1.class and call the method directly:

 private void handleRuntimeException( final RuntimeException e, final HttpServletResponse response ) throws Throwable
{
    if (e instanceof Exception1) handleAnException(e,response);
    else throw e.getCause(); // Can be of many types
}
Mayday
  • 4,680
  • 5
  • 24
  • 58
  • That if check wouldn't work because RuntimeException is never going to be a subclass of Exception1. Furthermore, I know that I can explicitly call handleAnException with e.getCause() already - in the case where e.getCause() can return many different types of exceptions it would end up with a big ugly block of if-else-if statements manually calling the appropriate handler. You then have to remember to explicitly call the correct handler for that type and apply a new if clause for each type. It would be much cleaner if you can just rethrow the exception and have the ControllerAdvice catch it too – agentgonzo Mar 29 '16 at 12:06
  • Sorry, I missunderstood, and thought Exception1 would be extending the RuntimeException class, and that was the reason why it would not go through that handler. Is not as easy as throw a new Exception1(e.getCause()); instead of throwing e.getCause()? – Mayday Mar 29 '16 at 12:24
  • 1
    Unfortunately not, throwing a new exception doesn't get caught by the handler, it just goes straight up-and-out of the handler and causes a 500 error in the web container – agentgonzo Mar 30 '16 at 12:39
0

Few years late on this.. but just ran into a need for this in dealing with @Async services - when throwing an exception, they get wrapped in the ExecutionException.class and wanted my controller advice to direct them to their proper handler, an identical situation you were in.

Using reflection, can gather all the methods on the controller advice, sift for the matching @ExceptionHandler annotation for e.getCause().getClass() then invoke the first found method.

@ControllerAdvice
public class ExceptionHandlingAdvice
{
    @ExceptionHandler( RuntimeException.class )
    private void handleRuntimeException( final RuntimeException e, final HttpServletResponse response )
    {
        if (e.getCause() != null) {
            Optional<Method> method = Arrays.stream(Rest.Advice.class.getMethods())
                    .filter(m -> {
                        // Get annotation
                        ExceptionHandler annotation = m.getAnnotation(ExceptionHandler.class);
                        // Annotation exists on method and contains cause class
                        return annotation != null && Arrays.asList(annotation.value()).contains(e.getCause().getClass());
                    })
                    .findFirst();

            if (method.isPresent()) {
                try {
                    method.get().invoke(this, e.getCause(), response);
                } catch (IllegalAccessException | InvocationTargetException ex) {
                    // Heard you like exceptions on your exceptions while excepting
                    ex.printStackTrace();
                }
            }
        }
        
        // Handle if not sent to another
    }

    ... other handlers
}

Didn't test with void -- Personally, I return ResponseEntity<MyStandardErrorResponse> from my handlers, so my invoke line looks like:

return (ResponseEntity<MyStandardErrorResponse>) method.get().invoke(this, e.getCause(), request);
Charly
  • 881
  • 10
  • 19