Exception Handling in Spring MVC
Spring MVC provides several complimentary approaches to exception handling but, when teaching Spring MVC, I often find that my students are confused or not comfortable with them.
Today I’m going to show you the various options available. Our goal is to not handle exceptions explicitly in Controller methods where possible. They are a cross-cutting concern better handled separately in dedicated code.
There are three options: per exception, per controller or globally.
NOTE: The demo applications has been revamped and updated (April 2018) to use Spring Boot 2.0.1 and is (hopefully) easier to use and understand. I also fixed some broken links (thanks for the feedback, sorry it took a while).
Spring Boot allows a Spring project to be setup with minimal configuration and it is likely that you are using it if your application is less than a few years old.
Spring MVC offers no default (fall-back) error page out-of-the-box. The most common way to set a default error page has always been the SimpleMappingExceptionResolver (since Spring V1 in fact). We will discuss this later.
However Spring Boot does provide for a fallback error-handling page.
At start-up, Spring Boot tries to find a mapping for /error . By convention, a URL ending in /error maps to a logical view of the same name: error . In the demo application this view maps in turn to the error.html Thymeleaf template. (If using JSP, it would map to error.jsp according to the setup of your InternalResourceViewResolver ). The actual mapping will depend on what ViewResolver (if any) that you or Spring Boot has setup.
If no view-resolver mapping for /error can be found, Spring Boot defines its own fall-back error page — the so-called “Whitelabel Error Page” (a minimal page with just the HTTP status information and any error details, such as the message from an uncaught exception). In the sample applicaiton, if you rename the error.html template to, say, error2.html then restart, you will see it being used.
If you are making a RESTful request (the HTTP request has specified a desired response type other than HTML) Spring Boot returns a JSON representation of the same error information that it puts in the “Whitelabel” error page.
Spring Boot also sets up a default error-page for the container, equivalent to the
directive in web.xml (although implemented very differently). Exceptions thrown outside the Spring MVC framework, such as from a servlet Filter, are still reported by the Spring Boot fallback error page. The sample application also shows an example of this.
A more in-depth discussion of Spring Boot error-handling can be found at the end of this article.
The rest of this article applies regardless of whether you are using Spring with or without Spring Boot.
Impatient REST developers may choose to skip directly to the section on custom REST error responses. However they should then read the full article as most of it applies equally to all web applications, REST or otherwise.
Using HTTP Status Codes
Normally any unhandled exception thrown when processing a web-request causes the server to return an HTTP 500 response. However, any exception that you write yourself can be annotated with the @ResponseStatus annotation (which supports all the HTTP status codes defined by the HTTP specification). When an annotated exception is thrown from a controller method, and not handled elsewhere, it will automatically cause the appropriate HTTP response to be returned with the specified status-code.
For example, here is an exception for a missing order.
And here is a controller method using it:
A familiar HTTP 404 response will be returned if the URL handled by this method includes an unknown order id.
Controller Based Exception Handling
You can add extra ( @ExceptionHandler ) methods to any controller to specifically handle exceptions thrown by request handling ( @RequestMapping ) methods in the same controller. Such methods can:
- Handle exceptions without the @ResponseStatus annotation (typically predefined exceptions that you didn’t write)
- Redirect the user to a dedicated error view
- Build a totally custom error response
The following controller demonstrates these three options:
In any of these methods you might choose to do additional processing — the most common example is to log the exception.
Handler methods have flexible signatures so you can pass in obvious servlet-related objects such as HttpServletRequest , HttpServletResponse , HttpSession and/or Principle .
Important Note: The Model may not be a parameter of any @ExceptionHandler method. Instead, setup a model inside the method using a ModelAndView as shown by handleError() above.
Exceptions and Views
Be careful when adding exceptions to the model. Your users do not want to see web-pages containing Java exception details and stack-traces. You may have security policies that expressly forbid putting any exception information in the error page. Another reason to make sure you override the Spring Boot white-label error page.
Make sure exceptions are logged usefully so they can be analyzed after the event by your support and development teams.
Please remember the following may be convenient but it is not best practice in production.
It can be useful to hide exception details in the page source as a comment, to assist testing. If using JSP, you could do something like this to output the exception and the corresponding stack-trace (using a hidden
For the Thymeleaf equivalent see support.html in the demo application. The result looks like this.
Global Exception Handling
Using @ControllerAdvice Classes
A controller advice allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation driven interceptor.
Any class annotated with @ControllerAdvice becomes a controller-advice and three types of method are supported:
- Exception handling methods annotated with @ExceptionHandler .
- Model enhancement methods (for adding additional data to the model) annotated with
@ModelAttribute . Note that these attributes are not available to the exception handling views.
- Binder initialization methods (used for configuring form-handling) annotated with
We are only going to look at exception handling — search the online manual for more on @ControllerAdvice methods.
Any of the exception handlers you saw above can be defined on a controller-advice class — but now they apply to exceptions thrown from any controller. Here is a simple example:
If you want to have a default handler for any exception, there is a slight wrinkle. You need to ensure annotated exceptions are handled by the framework. The code looks like this:
Any Spring bean declared in the DispatcherServlet ’s application context that implements HandlerExceptionResolver will be used to intercept and process any exception raised in the MVC system and not handled by a Controller. The interface looks like this:
The handler refers to the controller that generated the exception (remember that @Controller instances are only one type of handler supported by Spring MVC. For example: HttpInvokerExporter and the WebFlow Executor are also types of handler).
Behind the scenes, MVC creates three such resolvers by default. It is these resolvers that implement the behaviours discussed above:
- ExceptionHandlerExceptionResolver matches uncaught exceptions against suitable @ExceptionHandler methods on both the handler (controller) and on any controller-advices.
- ResponseStatusExceptionResolver looks for uncaught exceptions annotated by @ResponseStatus (as described in Section 1)
- DefaultHandlerExceptionResolver converts standard Spring exceptions and converts them to HTTP Status Codes (I have not mentioned this above as it is internal to Spring MVC).
These are chained and processed in the order listed — internally Spring creates a dedicated bean (the HandlerExceptionResolverComposite ) to do this.
Notice that the method signature of resolveException does not include the Model . This is why @ExceptionHandler methods cannot be injected with the model.
You can, if you wish, implement your own HandlerExceptionResolver to setup your own custom exception handling system. Handlers typically implement Spring’s Ordered interface so you can define the order that the handlers run in.
Spring has long provided a simple but convenient implementation of HandlerExceptionResolver that you may well find being used in your appication already — the SimpleMappingExceptionResolver . It provides options to:
- Map exception class names to view names — just specify the classname, no package needed.
- Specify a default (fallback) error page for any exception not handled anywhere else
- Log a message (this is not enabled by default).
- Set the name of the exception attribute to add to the Model so it can be used inside a View
(such as a JSP). By default this attribute is named exception . Set to null to disable. Remember that views returned from @ExceptionHandler methods do not have access to the exception but views defined to SimpleMappingExceptionResolver do.
Here is a typical configuration using Java Configuration:
Or using XML Configuration:
The defaultErrorView property is especially useful as it ensures any uncaught exception generates a suitable application defined error page. (The default for most application servers is to display a Java stack-trace — something your users should never see). Spring Boot provides another way to do the same thing with its “white-label” error page.
It is quite common to extend SimpleMappingExceptionResolver for several reasons:
- You can use the constructor to set properties directly — for example to enable exception logging and set the logger to use
- Override the default log message by overriding buildLogMessage . The default implementation always returns this fixed text:
- Handler execution resulted in exception
This code is in the demo application as ExampleSimpleMappingExceptionResolver
It is also possible to extend ExceptionHandlerExceptionResolver and override its
doResolveHandlerMethodException method in the same way. It has almost the same signature (it just takes the new HandlerMethod instead of a Handler ).
To make sure it gets used, also set the inherited order property (for example in the constructor of your new class) to a value less than MAX_INT so it runs before the default ExceptionHandlerExceptionResolver instance (it is easier to create your own handler instance than try to modify/replace the one created by Spring). See ExampleExceptionHandlerExceptionResolver in the demo app for more.
Errors and REST
RESTful GET requests may also generate exceptions and we have already seen how we can return standard HTTP Error response codes. However, what if you want to return information about the error? This is very easy to do. Firstly define an error class:
Now we can return an instance from a handler as the @ResponseBody like this:
What to Use When?
As usual, Spring likes to offer you choice, so what should you do? Here are some rules of thumb. However if you have a preference for XML configuration or Annotations, that’s fine too.
- For exceptions you write, consider adding @ResponseStatus to them.
- For all other exceptions implement an @ExceptionHandler method on a @ControllerAdvice class or use an instance of SimpleMappingExceptionResolver . You may well have SimpleMappingExceptionResolver configured for your application already, in which case it may be easier to add new exception classes to it than implement a @ControllerAdvice .
- For Controller specific exception handling add @ExceptionHandler methods to your controller.
- Warning: Be careful mixing too many of these options in the same application. If the same exception can be handed in more than one way, you may not get the behavior you wanted. @ExceptionHandler methods on the Controller are always selected before those on any @ControllerAdvice instance. It is undefined what order controller-advices are processed.
A demonstration application can be found at github. It uses Spring Boot and Thymeleaf to build a simple web application.
The application has been revised twice (Oct 2014, April 2018) and is (hopefully) better and easier to understand. The fundamentals stay the same. It uses Spring Boot V2.0.1 and Spring V5.0.5 but the code is applicable to Spring 3.x and 4.x also.
About the Demo
The application leads the user through 5 demo pages, highlighting different exception handling techniques:
- A controller with @ExceptionHandler methods to handle its own exceptions
- A contoller that throws exceptions for a global ControllerAdvice to handle
- Using a SimpleMappingExceptionResolver to handle exceptions
- Same as demo 3 but with the SimpleMappingExceptionResolver disabled for comparison
- Shows how Spring Boot generates its error page
A description of the most important files in the application and how they relate to each demo can be found in the project’s README.md.
The home web-page is index.html which:
- Links to each demo page
- Links (bottom of the page) to Spring Boot endpoints for those interested in Spring Boot.
Each demo page contains several links, all of which deliberately raise exceptions. You will need to use the back-button on your browser each time to return to the demo page.
Thanks to Spring Boot, you can run this demo as a Java application (it runs an embedded Tomcat container). To run the application, you can use one of the following (the second is thanks to the Spring Boot maven plugin):
Error Page Contents
Also in the demo application I show how to create a “support-ready” error page with a stack-trace hidden in the HTML source (as a comment). Ideally support should get this information from the logs, but life isn’t always ideal. Regardless, what this page does show is how the underlying error-handling method handleError creates its own ModelAndView to provide extra information in the error page. See:
- ExceptionHandlingController.handleError() on github
- GlobalControllerExceptionHandler.handleError() on github
Spring Boot and Error Handling
Spring Boot allows a Spring project to be setup with minimal configuration. Spring Boot creates sensible defaults automatically when it detects certain key classes and packages on the classpath. For example if it sees that you are using a Servlet environment, it sets up Spring MVC with the most commonly used view-resolvers, hander mappings and so forth. If it sees JSP and/or Thymeleaf, it sets up these view-technologies.
Fallback Error Page
How does Spring Boot support the default error-handling described at the beginning of this article?
- In the event of any unhanded error, Spring Boot forwards internally to /error .
- Boot sets up a BasicErrorController to handle any request to /error . The controller adds error information to the internal Model and returns error as the logical view name.
- If any view-resolver(s) are configured, they will try to use a corresponding error-view.
- Otherwise, a default error page is provided using a dedicated View object (making it independent of any view-resolution system you may be using).
- Spring Boot sets up a BeanNameViewResolver so that /error can be mapped to a View of the same name.
- If you look in Boot’s ErrorMvcAutoConfiguration class you will see that the defaultErrorView is returned as a bean called error . This is the View bean found by the BeanNameViewResolver .
The “Whitelabel” error page is deliberately minimal and ugly. You can override it:
- By defining an error template — in our demo we are using Thymeleaf so the error template is in src/main/resources/templates/error.html (this location is set by the Spring Boot property spring.thymeleaf.prefix — similar properties exist for other supported server-side view technologies such as JSP or Mustache).
- If you aren’t using server-side rendering
2.1 Define your own error View as a bean called error .
2.1 Or disable Spring boot’s “Whitelabel” error page by setting the property
server.error.whitelabel.enabled to false . Your container’s default error page is used instead.
By convention, Spring Boot properties are normally set in application.properties or application.yml .
Integration with SimpleMappingExceptionResolver
What if you are already using SimpleMappingExceptionResolver to setup a default
error view? Simple, use setDefaultErrorView() to define the same view that Spring Boot uses: error .
Note that in the demo, the defaultErrorView property of the SimpleMappingExceptionResolver is deliberately set not to error but to defaultErrorPage so you can see when the handler is generating the error page and when Spring Boot is responsible. Normally both would be set to error .
Container-Wide Exception Handling
Exceptions thrown outside the Spring Framework, such as from a servlet Filter, are also reported by Spring Boot’s fallback error page.
To do this Spring Boot has to register a default error page for the container. In Servlet 2, there is an directive that you can add to your web.xml to do this. Sadly Servlet 3 does not offer a Java API equivalent. Instead Spring Boot does the following:
- For a Jar application, with an embedded container, it registers a default error page using Container specific API.
- For a Spring Boot application deployed as a traditional WAR file, a Servlet Filter is used to
catch exceptions raised further down the line and handle it.