Spring MVC REST Exception Handling Best Practices

Spring MVC REST Exception Handling Best Practices (Part 1)

If you’re already using Spring to build your application, and you need to serve a ReST API,Spring MVC can be a good choice to write your REST endpoints.

However, representing errors or problems cleanly in a RESTful way may not be immediately obvious since Spring MVC is so often referenced for building user interfaces. Because there is no direct UI concept in REST APIs, how then do you use Spring MVC to represent errors or problems in a clean and intuitive way?

This article is Part 1 of a 2 part series. In this article, we’ll cover RESTful error reporting best practice conventions. Part 2 will show how to implement them with Spring MVC in a complete example web application.

RESTful Error Design

If an error occurs, RESTful practices expect that we set an HTTP status code in the response to generally classify why the request failed. It is considered best practice by most REST API designers to re-use the status codes in the HTTP specification whenever possible, since so many existing HTTP clients understand most of these error conditions already and re-use encourages consistent behavior, which is always good for development.

However, there are only 24 commonly referenced HTTP status codes that are used for representing errors; there are 18 4xx codes representing client errors and 6 5xx codes representing server errors (there are other codes defined in specifications like WebDav, but they are not ubiquitous). A problem here is that these 24 are really general purpose codes - they likely don’t describe everything there is to know about a particular problem.

To best help your REST API customers, you ideally want to give them as much information as possible to help them diagnose and hopefully fix the problem. The name of the game is simplicity: the easier it is for them to use your REST API, the more likely it is they will adopt your service.

REST Error Representation

Because the status code is probably not enough, what else then can we provide to assist our end users whenever they encounter errors? An obvious item is human readable error message, to be easily read by developers. But there is even more information that we can add to provide a truly intuitive and helpful error representation.

The folks at Apigee have put together a nice blog article about RESTful error representations (and some great videos too) worth checking out. We want to do something similar.

Here is an example of what we consider a ‘best practice’ REST error response body representation to show relevant error data (the example is JSON, but XML would be fine too depending on what your API serves):

{
    "status": 404,
    "code": 40483,
    "message": "Oops! It looks like that file does not exist.",
    "developerMessage": "File resource for path /uploads/foobar.txt does not exist.  Please wait 10 minutes until the upload batch completes before checking again.",
    "moreInfo": "http://www.mycompany.com/errors/40483"
}

We’ll describe the properties next.

status

The status property is merely the same HTTP status code (integer) in the response header. This is a convenience mechanism: by including the status code in the body, any REST client that processes the error has one and only one place to look to fully understand the error: the error representation itself.  There is no need to check header values or other locations to understand the message.

code

The code property is an error code specific to your particular REST API. It is usually something that conveys information very specific to your problem domain.

This is convenient because of the limitation of having only 24 widely known general purpose HTTP error codes. By using your own codes, you can convey much more specific and richer reasons as to why something failed. Again, the more information that the API client can receive, the better.

In the example above, the code property has a value of 40483. While the general purpose“status”: 404 indicates that the requested resource was not found, perhaps there is an application-specific code of 40483 that indicates not only that the resource wasn’t found, but it wasn’t found due to the fact that it wasn’t yet uploaded to the server.

Granted, this particular ‘uploaded file’ example is somewhat contrived, but the key point here is that your API can convey a much richer set of error information if you leverage your own codes.

TIP:  If your application does not have a specific error code for a particular error, it can be a good idea to default the code value to be the same as the status value. This ensures that the client always sees a code value and does not need to perform ‘does this property exist?’ logic. This is cleaner/easier for API consumers, and that’s a good thing for adoption.

message

The message property is a nice human readable error message that can potentially be shown directly to an application end user (not a developer).  It should be friendly and easy to understand and convey a concise reason as to why the error occurred.  It should probaby not contain technical information.  Technical information should be in the developerMessageproperty instead (covered next).

Why is this useful?

If you have a REST API consumer (e.g. your customer), and that consumer wishes to relay themessage value directly to the end user, they can do so.  This allows API consumers to write user interfaces and support their own end-users very quickly without much work on their end.  The more things you do to save them time and keep them happy using your API, the better.

developerMessage

The developerMessage property conveys any and all technical information that a developer calling your REST API might find useful.  This is where you might include exception messages, stack traces, or anything else that you think will help a developer.

moreInfo

The moreInfo property specifies a URL that anyone seeing the error message can click (or copy and paste) in a browser.  The target web page should describe the error condition fully, as well as potential solutions to help them resolve the error condition.  

This is probably THE most important property of all because the target web page can be freely formatted to represent whatever information you wish.  You could have links to your support department, ‘get help now’ chat dialogs, or whatever else you think might be useful.  Show the developers love, and they’ll continue using your API.

Twilio for example has a great Error and Warning Dictionary that shows this concept in action.  Learn it, love it, replicate it.  Again, as much information as is possible is desired.  Supply whatever you might think is necessary to help your API consumers/customers and keep them happy.

Part 2

Continue on to part 2, where we show you how to ensure these error messages are easily returned from a Spring MVC-based REST API.

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 @ExceptionHandlerannotation.

HandlerExceptionResolvers are great for handling Exceptions in a uniform way and are ideal as framework components that can do heavy lifting for you. The @ExceptionHandlerannotation 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 HandlerExceptionResolverapproach is ideal. We’ll present a REST-friendly HandlerExceptionResolverimplementation 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 ownHandlerExceptionResolver 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 RestExceptionHandlerwill:

  1. Convert the Exception into a RestError instance. The RestError implementationembodies 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 mainRestExceptionHandler 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 andDefaultController.

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 anUnknownResourceException 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:

<bean id="restExceptionResolver" class="com.stormpath.spring.web.servlet.handler.RestExceptionHandler">
  <property name="order" value="100"></property>
  <property name="errorResolver">
    <bean class="com.stormpath.spring.web.servlet.handler.DefaultRestErrorResolver">
      <property name="localeResolver" ref="localeResolver"></property>
      <property name="defaultMoreInfoUrl" value="mailto:support@mycompany.com"></property>
      <property name="exceptionMappingDefinitions">
        <map>
          <!-- 404 -->
          <entry key="com.stormpath.blog.spring.mvc.rest.exhandler.UnknownResourceException" value="404, _exmsg"></entry>
          <!-- 500 (catch all): -->
          <entry key="Throwable" value="500, error.internal"></entry>
        </map>
      </property>
    </bean>
  </property>
</bean>

If you look closely, you’ll see that this particular example of the RestExceptionHandlerconfiguration doesn’t have much to it directly. We have two properties: order anderrorResolver. (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 anAnnotationMethodHandlerExceptionResolver bean (e.g. order 0) so you can use the@ExceptionHandler annotation for custom exception handling strategies and then have theRestExceptionHandler (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 RestErrorproperties. Definitions can include, in order of precedence:

PrecedenceRestError propertyExplicit definition key
1statusstatus
2codecode
3messagemsg
4developerMessagedevMsg
5moreInfoURLinfoUrl

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 thedeveloperMessage 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 .propertiesfile 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:

<entry key="com.stormpath.blog.spring.mvc.rest.exhandler.UnknownResourceException" value="404, _exmsg"></entry>

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

<entry key="Throwable" value="500, error.internal"></entry>

This second entry can be summarized as “If encountering any unhandled Throwable, return aRestError 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. theRestError message value will be equal to invokingmessageSource.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 implementsMessageSourceAware 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.

Reference: https://stormpath.com/blog/spring-mvc-rest-exception-handling-best-practices-part-1/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值