javaspringspring-bootspring-mvccontroller-advice

Spring @Controller exception handler and global exception handler. How to invoke both


I have a global exception handler to share across REST @Controllers. For this I use a @ControllerAdvice with some @ExceptionHandler methods. This works fine. Now, if I add an @ExceptionHandler in a particular Rest Controller then that new handler takes precedence over the global exception handler and the global one is just never called.

What I need is actually to have both called. The order doesn't matter. The point is that there is some global, controller-agnostic error handling code and also some controller-specific error handling and I need both to execute. Is this possible? e.g. Can I somehow in the controller-specific handler (which is called first) mark the exception handling as not handled so the next handler in line is invoked?

I know I could inject the @ControllerAdvice in the @Controller and invoke the global handler from the specific one myself, but I rather keep the controller decoupled from the global exception handler


Solution

  • I don't think you can do this with out-of-the-box Spring. If you look under the hood at this method ExceptionHandlerExceptionResolver#doResolveHandlerMethodException, you can see that at first Spring looking for single method that will handle occurred exception:

        ...
        ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
        if (exceptionHandlerMethod == null) {
            return null;
        }
        ...
    

    You can also look at the implementation of getExceptionHandlerMethod method. First its trying to find appropriate handler within you controller methods, if nothing found - then within controller advisors.

    After that it invokes it:

        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
            }
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
        }
        catch (Exception invocationEx) {
            if (logger.isErrorEnabled()) {
                logger.error("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
            }
            return null;
        }
    

    You should also note that Spring swallows any exception that might occur during original exception handling, so you can't even throw new exception from your first handler or rethrow original exception so it can be catched somewhere else (You can actually, but this is pointless).

    So, if you really want to do this - I guess the only way is to write you own ExceptionHandlerExceptionResolver (maybe extend Springs ExceptionHandlerExceptionResolver) and modify doResolveHandlerMethodException method, so it looks for multiply exceptionHandlerMethod (one within controllers and one within advisors) and invokes it in a chain. This might be tricky :)

    Also, you can look at this Jira ticket.

    Hope it helps.