Spring MVC REST Exception Handling Best Practices (part 2)

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

Spring MVC has two primary ways of handling exceptions that are thrown when invoking an MVC Controller: the HandlerExceptionResolver and the @ExceptionHandler annotation.

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.

RestExceptionHandler

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.

Tip: This code discussed in this article, as well as a sample Spring MVC REST application, is available via Stormpath’s Github as the spring-mvc-rest-exhandler repository.

When your Spring MVC REST Controllers throw an Exception, the RestExceptionHandler will:

  1. Convert the Exception into a RestError instance. The RestError implementation embodies all of the Rest Error Representation properties discussed previously in Part 1
  2. Set the appropriate HTTP Response Status Code on the HTTP response based on the constructed RestError.
  3. 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.

Example Application

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.

Endpoints

After starting the application, you can visit the following two REST endpoints:

http://localhost:8080/v1/users/jsmith

http://localhost:8080/v1/users/djones

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:

http://localhost:8080/v1/users/doesNotExist

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.

MVC Controllers

The example application has two controllers – a UserController and DefaultController.

UserController

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).

DefaultController

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).

Order

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.

RestErrorResolver

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:

PrecedenceRestError propertyExplicit definition key
1 status status
2 code code
3 message msg
4 developerMessage devMsg
5 moreInfoURL infoUrl

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

Implicit:

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!

Summary

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.

Comments

May 24, 2012 | 1:51pm

Looks great, I am already trying to add it to my project. I am just having problems in setting error response for json and xml depending on the call.
Any advice or sample?

May 24, 2012 | 2:35pm

Hi Marcelo,

The RestError instance is converted to an object suitable for rendering via the RestErrorConverter.  In the example project, we use a default RestErrorConverter that converts to a Map.

The example app's configured restExceptionHandler bean on line 60 specifies a Spring HttpMessageConverter.  You can register multiple converters that can render different content types (e.g. JSON, XML, etc).  The first HttpMessageConverter that is discovered to support the HTTP request's preferred Content-Type header (as ordered in the header value) will be used to render the response.

Cheers,

Les

May 29, 2012 | 1:28am

Very good example! Will adopt it for sure!

I have struggled to import sources to STS 2.9.2 - with m2e plugin for maven. I have fixed the problem by renaming root folder (from 'spring-mvc-rest-exhandler' to 'spring-mvc-rest-exhandler-root') after checking it out from GIT. I hope that will save some time to others.

August 6, 2012 | 11:00pm

This is very good, but I get a warning in RestExceptionHandler.java on line 255:

for (HttpMessageConverter messageConverter : converters) {

The issue is that HttpMessageConverter isn't typed here, so I tried changing it as follows:

for (HttpMessageConverter<?> messageConverter : converters) {

This broke something just a few lines down on line 258:

messageConverter.write(body, acceptedMediaType, outputMessage);

The error is:

The method write(capture#11-of ?, MediaType, HttpOutputMessage) in the type HttpMessageConverter<capture#11-of ?> is not applicable for the arguments (Object, MediaType, HttpOutputMessage)

Any ideas on a way to deal with this?

August 14, 2012 | 11:07pm

I love this approach, it's elegant and unobtrusive. It would be absolutely fantastic if this could be published as an artefact to maven central; or do you have this on some other public maven repository?

Integrating into spring would be brilliant as the default of returning an HTML page for a fault when the "Accept:" request parameter was set to something else is not very RESTful.

I thank you again for this.

August 15, 2012 | 9:31pm

After implementing this I wondered if there was a simpler way to get HttpMessageConverter instances without needing to register them twice in the container. For this I added an @Autowired RequestMappingHandlerAdapter instance, then instead of using the HttpMessageConverterHelper to get default instances (which duplicates them again) I used

converters.addAll(requestMappingHandlerAdapter.getMessageConverters());

This has the advantage that any converters registered inside <message-converters register-defaults="true"/> will both add additional and defaults.

August 15, 2012 | 10:17pm

I wanted to get XML output, but of course JAXB2 can not serialise Map instances. Why not just serialise the RestError bean itself instead of converting to a Map?

I created a BasicRestErrorConverter that returns a simple object mirroring the RestError class with annotations for jackson and XML attributes. I did this to not interfere with the original implementation, otherwise I would simply annotate RestError itself.

This IMHO is more flexible, though since a converter can be injected it's fairly straightforward to create your own.

Thanks :)

August 16, 2012 | 12:49am

When not using the "catch-all" controller, spring will return a 406 (Not Acceptable) if you requested a valid resource but had an unacceptable "Accept" header.

How can you get this same behaviour? The problem with the catch-all is you don't know if it was matched by a more specific request, or is there a way to discover this?

January 27, 2013 | 11:12pm

When I am calling from my browser the rest error object is returned fine in json format. But When I make a jersey client to test my rest webservice following message is given in the console:

GET http://localhost:8082/SpringWebService/provider/movies/random returned a response status of 500 Internal Server Error
Exception in thread "main" com.sun.jersey.api.client.UniformInterfaceException: GET http://localhost:8082/SpringWebService/provider/movies/year/jfv returned a response status of 500 Internal Server Error
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:686)
at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:507)
at com.sapient.test.ClientTest.main(ClientTest.java:24)

-------------------------------------------------------------
What I want it is to error object to the client when an exception occurs.

Can u please help.

January 28, 2013 | 9:31am

Hi Chetan. Thanks for the question! Can you send us an email at support@stormpath.com so we can get that answered for you?

Thanks!

Leave a comment