5

Ours is a Spring MVC based REST application. I am trying to use ExceptionHandler annotation to handle all errors and exceptions.

I have

    @ExceptionHandler(Throwable.class)
    public @ResponseBody String handleErrors() {
        return "error";
    }

This works whenever there is an exception thrown and it doesn't work for any errors.

I am using Spring 4.0. Is there any work-around?

l a s
  • 3,328
  • 10
  • 35
  • 58
  • 2
    Is there any reasonable, good, rational explanation why you would want to handle `Error` as *well* as `Exception` and `RuntimeException`? Handling `Throwable` just feels... ***wrong*** to me. – Makoto Mar 21 '14 at 01:27
  • 2
    I want to respond with a meaningful json to the front end app and don't want to respond with 500 / stacktrace. – l a s Mar 21 '14 at 01:31
  • Sometimes a 500 *is* meaningful - it means that something on the server has gone catastrophically wrong. Simply saying "error" without context doesn't do any justice on what really happened. – Makoto Mar 21 '14 at 01:35
  • 1
    @Makoto as for the user this doesn't matter - whether something went catastrophically wrong or just wrong - in the end, some functionality is not working, and one should show appropriate error. – Yuriy Nakonechnyy Jul 08 '14 at 16:44
  • This has just been fixed - please refer to this answer: http://stackoverflow.com/questions/5153132/spring3-exceptionhandler-for-servletrequestbindingexception/36334660#answer-36334660 – Yuriy Nakonechnyy Mar 31 '16 at 13:21

2 Answers2

7

Contrary to what the ExceptionHandler#value() attribute indicates

Class<? extends Throwable>[] value() default {};

and @ExceptionHandler is only meant to handle Exception and its sub types.

Spring uses ExceptionHandlerExceptionResolver to resolve your annotated handlers, using the following method

doResolveHandlerMethodException(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) 

which as you can see only accepts an Exception.

You cannot handle Throwable or Error types with @ExceptionHandler with this configuration.

I would tell you to provide your own HandlerExceptionResolver implementation which does handle Throwable instances, but you'd need to provide your own DispatcherServlet (and most of the MVC stack) yourself since DispatcherServlet does not catch Throwable instances at any place where you could make any significant difference.


Update:

Since 4.3, Spring MVC wraps a thrown Throwable value in a NestedServletException instance and exposes that to the ExceptionHandlerExceptionResolver.

Sotirios Delimanolis
  • 252,278
  • 54
  • 635
  • 683
  • How do i go about this? Is there any design suggestion? How about a Servlet Filter or an interceptor? – l a s Mar 21 '14 at 01:32
  • @las `Throwable` and `Error` are much more serious than could be handled by responding to your client. Don't handle them. Just configure and deploy so that they don't happen (as much as possible). – Sotirios Delimanolis Mar 21 '14 at 01:34
  • 4
    @SotiriosDelimanolis I totally agree but imho that's a serious limitation of Spring. One legitimate case of handling all `Throwable` instances is custom logging of them. – Yuriy Nakonechnyy Jul 08 '14 at 17:04
  • Also, as per following comment of `Juergen Hoeller` - one of Spring's authors - https://jira.spring.io/browse/SPR-13788?focusedCommentId=122373&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-122373 - `even OutOfMemoryError is absolutely recoverable in some cases`, so it makes a lot of sense to catch them. – Yuriy Nakonechnyy Mar 31 '16 at 07:45
-1

You can do a kind of Hacking to capture Error in Spring MVC. First, define an Interceptor like this :

public class ErrorHandlingInterceptor extends HandlerInterceptorAdapter {
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
{
    super.afterCompletion(request, response, handler, ex);

    controller.handleError(ex.getCause(), request, response);
} }

Second, define a method in your controller like "handleError" method:

    ErrorResponse errorResponse = new ErrorResponse();
    errorResponse.setExceptionId(exceptionId);
    errorResponse.setErrorMsg(ex.toString());
    errorResponse.setServerStackTrace(serverStackTrace(ex));

    response.setStatus(responseCode);
    response.setContentType("application/json");

    ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
    writer.writeValue(response.getOutputStream(), errorResponse);

Finally, config your interceptor in Spring configuration.

<mvc:interceptors>
    <bean class="ErrorHandlingInterceptor" />
</mvc:interceptors>

Code in DispatchServlet:

            catch (Exception ex) {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// This is where to handle Exception by Spring.
// If Error happens, it will go to catch Error statement 
// which will call afterCompletion method
            mv = processHandlerException(processedRequest, response, handler, ex);
            errorView = (mv != null);
        }

        // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
            render(mv, processedRequest, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                        "': assuming HandlerAdapter completed request handling");
            }
        }

        // Trigger after-completion for successful outcome.
        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
    }

    catch (Exception ex) {
        // Trigger after-completion for thrown exception.
        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
        throw ex;
    }
    catch (Error err) {
        ServletException ex = new NestedServletException("Handler processing failed", err);
        // Trigger after-completion for thrown exception.
        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
        throw ex;
    }
sendon1982
  • 7,088
  • 42
  • 36
  • "mv = processHandlerException(processedRequest, response, handler, ex);" is where to handle Exception and only Exception by Spring. If Error happens, it will go to catch Error statement which will call afterCompletion method which will convert Error to Exception and invoked by your Interceptor – sendon1982 Apr 23 '15 at 04:05