How to write a proper global error handler with Spring MVC / Spring Boot

56,638

Have a look at ControllerAdvice You could do something like this:

@ControllerAdvice
public class ExceptionHandlerController {

    public static final String DEFAULT_ERROR_VIEW = "error";

    @ExceptionHandler(value = {Exception.class, RuntimeException.class})
    public ModelAndView defaultErrorHandler(HttpServletRequest request, Exception e) {
            ModelAndView mav = new ModelAndView(DEFAULT_ERROR_VIEW);

        mav.addObject("datetime", new Date());
        mav.addObject("exception", e);
        mav.addObject("url", request.getRequestURL());
        return mav;
    }
}
Share:
56,638
kayahr
Author by

kayahr

„People assume that a Closure is a function having access to the parent scope, even after the parent function has closed, but actually from a non-linear, non-subjective viewpoint - it's more like a big ball of wibbly wobbly... timey wimey... stuff.“ – The JavaScript Doctor

Updated on September 19, 2020

Comments

  • kayahr
    kayahr over 3 years

    I'm writing a web application with Spring 4.0.4 and Spring Boot 1.0.2 using Tomcat as embedded web container and I want to implement a global exception handling which intercepts all exceptions and logs them in a specific way. My simple requirements are:

    • I want to globally handle all exceptions which are not already processed somewhere else (In a controller exception handler for example). I want to log the message and I want to display a custom error message to the user.
    • I don't want Spring or the web container to log any errors by itself because I want to do this myself.

    So far my solution looks like this (Simplified, no logging and no redirection to an error view):

    @Controller
    @RequestMapping("/errors")
    public class ErrorHandler implements EmbeddedServletContainerCustomizer
    {
        @Override
        public void customize(final ConfigurableEmbeddedServletContainer factory)
        {
            factory.addErrorPages(new ErrorPage("/errors/unexpected"));
            factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/errors/notfound"));
        }
    
        @RequestMapping("unexpected")
        @ResponseBody
        public String unexpectedError(final HttpServletRequest request)
        {
            return "Exception: " + request.getAttribute("javax.servlet.error.exception");
        }
    
        @RequestMapping("notfound")
        @ResponseBody
        public String notFound()
        {
            return "Error 404";
        }
    }
    

    The result is that exceptions thrown in controllers are correctly handled by the unexpectedError method and 404 status codes are handled by the notFound method. So far so good, but I have the following problems:

    • Tomcat or Spring (not sure who is responsible) is still logging the error message. I don't want that because I want to log it myself (with additional information) and I don't want duplicate error messages in the log. How can I prevent this default logging?
    • The way I access the exception object doesn't seem right. I fetch if from the request attribute javax.servlet.error.exception. And that's not even the thrown exception, it is an instance of org.springframework.web.util.NestedServletException and I have to dive into this nested exception to fetch the real one. I'm pretty sure there is an easier way but I can't find it.

    So how can I solve these problems? Or maybe the way I implemented this global exception handler is completely wrong and there is a better alternative?

  • kayahr
    kayahr almost 10 years
    Thanks, this works great. But now I discovered another requirement which is not part of my original question so I created a new one. Maybe you can help there, too: stackoverflow.com/questions/23582534/…
  • Matthew Cachia
    Matthew Cachia almost 8 years
    Cleanest solution available. One other worthy addition might be the @ResponseStatus to 500, which would return the response in HTTP 500 as expected.
  • Kacper86
    Kacper86 over 7 years
    Please note that this solution will also catch Spring Security exceptions like AuthenticationException. As a result, redirection to protected resource after login will not work. Explanation: entering protected resource normally redirects you to login page. After logging in, you should go back to protected resource (Spring Security takes care for that by catching AuthenticationException), but when you catch all exceptions like that, Spring Security will not catch it anymore.
  • Amit Sharma
    Amit Sharma about 7 years
    Your solution is missing a annotation to work. @@EnableWebMvc @@ControllerAdvice public class GlobalExceptionHandler {}
  • Roger
    Roger about 7 years
    @Kacper86 I believe you could use a separate @ControllerAdvice class, have them both implement Ordered and give the more specific Exception handling class higher precedence.
  • zygimantus
    zygimantus about 7 years
    Does this work with 404 errors?, etc.: No mapping found for HTTP request with URI […] in DispatcherServlet”?
  • min
    min almost 7 years
    for restful api, you may need use @RestControllerAdvice instead.
  • Ruslan Stelmachenko
    Ruslan Stelmachenko over 6 years
    Also note, this solution will break all @ResponseStatus exception classes, because as soon as any exception handler handles the exception, it counts as resolved and ResponseStatusExceptionResolver don't hit. ResponseStatusExceptionResolver looks for uncaught exceptions annotated by @ResponseStatus. But this global exception handler will catch them earlier.
  • Parker
    Parker over 6 years
    In some cases you may also want to handle ClientAbortExceptions.