agentgonzo
agentgonzo

Reputation: 3653

Throwing an exception from @ExceptionHandler to get caught by another handler

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?

Upvotes: 11

Views: 4131

Answers (2)

Charly
Charly

Reputation: 1139

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);

Upvotes: 0

Mayday
Mayday

Reputation: 5146

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
}

Upvotes: 1

Related Questions