How to write a proper global error handler with Spring MVC / Spring Boot
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;
}
}
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, 2020Comments
-
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 thenotFound
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 oforg.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 almost 10 yearsThanks, 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 almost 8 yearsCleanest solution available. One other worthy addition might be the @ResponseStatus to 500, which would return the response in HTTP 500 as expected.
-
Kacper86 over 7 yearsPlease 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 about 7 yearsYour solution is missing a annotation to work. @@EnableWebMvc @@ControllerAdvice public class GlobalExceptionHandler {}
-
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 about 7 yearsDoes this work with 404 errors?, etc.:
No mapping found for HTTP request with URI […] in DispatcherServlet”?
-
min almost 7 yearsfor restful api, you may need use
@RestControllerAdvice
instead. -
Ruslan Stelmachenko over 6 yearsAlso note, this solution will break all
@ResponseStatus
exception classes, because as soon as any exception handler handles the exception, it counts as resolved andResponseStatusExceptionResolver
don't hit.ResponseStatusExceptionResolver
looks for uncaught exceptions annotated by@ResponseStatus
. But this global exception handler will catch them earlier. -
Parker over 6 yearsIn some cases you may also want to handle ClientAbortExceptions.