kayahr kayahr - 2 months ago 24
Java Question

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

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?

Answer

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;
    }
}
Comments