In part 1 of this 2-part series, we discussed a best-practice error representation (format) that should be returned to a REST API caller when an error is encountered.
In this article (part 2), we’ll show how to produce those representations from a REST API written using Spring MVC.
Spring Exception Handling
HandlerExceptionResolvers are great for handling Exceptions in a uniform way and are ideal as framework components that can do heavy lifting for you. The @ExceptionHandler annotation is a good choice if you want to manually perform some logic based on a specific Exception instead.
Both of these mechanisms are extremely powerful: our normal code can throw exceptions as expected in a type-safe manner to indicate exactly why something failed, with all of the OO benefits that implies. Then we can let the resolver and/or handler do the Exception–to– HTTP translation work as necessary. You know - separation of concerns and all that good stuff.
For our REST exception handling, we want all errors to be represented in the same way in a best-practices manner. Because of this uniformity, the HandlerExceptionResolver approach is ideal. We’ll present a REST-friendly HandlerExceptionResolver implementation that can be used in any REST API.
While most existing HandlerExceptionResolver implementations are suitable for Spring MVC-based user interfaces, they don’t quite hit that ‘sweet spot’ for REST error handling. To get exactly what we want based on Part 1’s representation format, we’ve created our own HandlerExceptionResolver implementation, called the RestExceptionHandler.
When your Spring MVC REST Controllers throw an Exception, the RestExceptionHandler will:
- Convert the Exception into a RestError instance. The RestError implementation embodies all of the Rest Error Representation properties discussed previously in Part 1.
- Set the appropriate HTTP Response Status Code on the HTTP response based on the constructed RestError.
- Render the RestError representation to the HTTP Response body. By default, we render a JSON body just like Part 1’s example.
We will cover how the RestExceptionHandler works in detail soon enough. Let’s take a look at the RestExceptionHandler in action in an example project so you can see how it works.
You can check out the spring-mvc-rest-exhandler Github project which includes the main RestExceptionHandler implementation and an additional sample application.
After checking out the project, you can build it with Maven:
(At the root of the project):
$ mvn clean install
This builds both the RestExceptionHandler library (a .jar) and a separate peer example web application (a .war). You can run the example application in the example directory:
$ cd example $ mvn jetty:run
This will start the Jetty web server on localhost, port 8080.
After starting the application, you can visit the following two REST endpoints:
But those are just (trivial) sample resources that render just fine. What we really care about for this article is: what does an error look like?
Anything else under the /v1/ path will render an HTTP 404 Not Found, with a Rest Error Representation body that we want. Try visiting the following URL:
You will see our nice REST error representation! Nice and clean…
But how did this work? An exception was thrown because the resource doesn’t exist, and something translated that exception to a nice JSON error message. Let’s look at how this happened.
The example application has two controllers – a UserController and DefaultController.
The UserController source code shows that it is a very simple Spring MVC controller. It simulates a successful lookup of two sample users and throws a custom (application-specific) UnknownResourceException when a user can’t be found.
We expect the UnknownResourceException to be automatically translated to an HTTP 404 (Not Found) with a nice Error representation via our RestExceptionHandler configuration (we’ll cover that soon).
The DefaultController source code shows that it functions as more of an infrastructural component. Via its @RequestMapping, we see that it is the default controller that is invoked by Spring any time Spring couldn’t find a more specific controller.
The DefaultController is extremely simple: it always throws an UnknownResourceException in all cases. This is good for a REST application because we always want to show a relevant error body when no other endpoints can service a request.
So we see that the MVC Controllers are throwing nice type-safe exceptions as desired. Now let’s take a look at how the RestExceptionHandler translates those Exceptions into the HTTP error body and how you can customize its behavior.
RestExceptionHandler Spring Configuration
Here is a basic RestExceptionHandler Spring bean definition:
If you look closely, you’ll see that this particular example of the RestExceptionHandler configuration doesn’t have much to it directly. We have two properties: order and errorResolver. (We can configure other properties as well, like HttpMessageConverter and more, but that is out of scope for this article).
The order property is useful if you want to have the RestExceptionHandler function in a chain if other HandlerExceptionResolvers need to be configured.
This allows you to, for example, configure an AnnotationMethodHandlerExceptionResolver bean (e.g. order 0) so you can use the @ExceptionHandler annotation for custom exception handling strategies and then have the RestExceptionHandler (e.g. order 100) act as a ‘catch all’ default for all other Exceptions. The example application’s rest-spring.xml file demonstrates this technique.
However, it is easy to see that the errorResolver property is the bulk of configuration. We’ll cover that next.
The RestExceptionHandler delegates the runtime Exception-to-RestError resolution logic to a RestErrorResolver instance. The RestErrorResolver knows how to, for a given Exception instance, return a RestError instance that represents the REST error representation we want to return to the REST client.
In the example Spring XML configuration above, the RestErrorResolver implementation is our DefaultRestErrorResolver. The DefaultRestErrorResolver relies on a set of mapping definitions to resolve the exception to a RestError instance.
For each mapping definition entry:
The entry key is any String that might appear in a fully qualified class name of an Exception (or any of the Exception’s super classes).
The entry value is a RestError definition as a comma-delimited String. The string is parsed using heuristics to determine how to build a RestError instance.
When the DefaultRestErrorResolver encounters an Exception at runtime, it will inspect the exception according to the mapping definitions, and if it finds a match, it will return the corresponding RestError instance.
The DefaultRestErrorResolver already has some default mappings for Spring-specific exceptions and a few other well-known exceptions, but these definitions can be overridden when you configure the bean.
The comma-delimited value definitions are heuristically parsed to obtain RestError properties. Definitions can include, in order of precedence:
|Precedence||RestError property||Explicit definition key|
Implicit definitions (those that do not use explicit definition keys) are evaluated based on precedence. That is, the following definitions are equivalent:
Explicit (one line, line break for formatting only):
status=500, code=10023, msg=error.notFound, moreInfoUrl=http://foo.com/docs/api/10023
500, 10023, error.notFound, http://foo.com/docs/api/10023
The latter is less verbose and can be more convenient.
Additionally, two special values are supported: _exmsg and _msg:
_exmsg indicates the message property should reflect to the runtime Exception’s message value, i.e. exception.getMessage()
_msg is valid only for the developerMessage property and it indicates that the developerMessage value should be the same as the message value.
Finally, it should be noted that because these definitions are simple key/value pairs, if your project has many of these definitions, it might make sense to define them in a .properties file instead of in the Spring XML directly. Some minor glue code or config can be used to read that file in at startup and set it as a Map on the DefaultRestErrorResolver.
Example Application Exception Mappings
The example application has two example exception mapping definitions in its rest-servlet.xml file:
The first entry can be summarized as “If encountering an UnknownResourceException, return a RestError instance with an HTTP Status of 404 and the message defaulted to the exception’s message.” (i.e. _exmsg is a token that indicates exception.getMessage() should be used).
This second entry can be summarized as “If encountering any unhandled Throwable, return a RestError instance with an HTTP Status of 500 and a message value returned by an available MessageSource using the message key of error.internal”. (i.e. the RestError message value will be equal to invoking messageSource.getMessage(“error.internal”, null, “error.internal”, locale) ).
The DefaultRestErrorResolver implementation even supports Internationalization (i18n) by being able to translate message codes to language-specific values. It implements MessageSourceAware to automatically pick up any registered MessageSource instances in your Spring application. It also allows for configuring a LocaleResolver to use when resolving locale-specific messages. This allows you to have language-specific error messages, depending on the REST request’s locale. Cool!
The new RestExceptionHandler presented here is quite flexible and supports very customizable error data, even internationalized messages, according to a best-practices REST error representation.
The referenced code and example application is licensed under the very business-friendly Apache 2 license, the same license that the Spring Framework uses. As such, we hope the good folks at Spring will incorporate these components into a future Spring release, and we're happy to assist in any way we can if they're interested.