A RESTful API service may throw exception in many cases. It’s important to handle them properly, so that we can provide a right HTTP response to the client, in particular a right status code (4xx or 5xx errors) and a correct entity. In this article, we will focus on « Exception Mapper », understand it’s mechanism in regards to exceptions.
After reading this article, you will understand:
- What is an exception mapper?
- How to declare an exception mapper in JAX-RS application?
- Exceptions matching mechanism
- When exception mapper throws an exception…
As usual, the source code is available for free on GitHub as mincong-h/jaxrs-2.x-demo. You can install and run the demo as following:
~ $ git clone https://github.com/mincong-h/jaxrs-2.x-demo.git ~ $ cd jaxrs-2.x-demo/exception exception $ mvn clean install exception $ java -jar target/exception-1.0-SNAPSHOT-jar-with-dependencies.jar
Before talking about exception mapper, we first need to understand the concept of provider. Providers in JAX-RS are responsible for various cross-cutting concerns such as filtering requests, converting representations into Java objects, mapping exceptions to responses, etc. By default, a single instance of each provider class is instantiated for each JAX-RS application, aka singletons.
ExceptionMapper<E extends Throwable> defines a contract for a
provider that maps Java exceptions
Response. Same as other providers,
exception mappers can be either pre-packaged in the JAX-RS runtime or supplied
by an application. In order to create your own exception mapper, you need to
create a class which implements interface
ExceptionMapper. Here’s an example
ExceptionA in your application:
When an exception of type
ExceptionA thrown by a JAX-RS resource, this
exception mapper can catch the exception and transform it into a HTTP 400
response, with the origin exception message as entity.
Exercise time: given the following JAX-RS resource method
newExceptionA1(), use command line tool cUrl to observe the HTTP response and
verify the exception mapping.
Command cUrl in terminal:
$ curl -i http://localhost:8080/a HTTP/1.1 400 Bad Request Content-Type: text/plain Connection: close Content-Length: 21 Mapper A: Exception A
So we successfully verified that
MapperA is used for handling
coming from resource method.
Declare Exception Mapper in JAX-RS Application
ExceptionMapper contract must be either
programmatically registered in a JAX-RS runtime or must be annotated with
@Provider annotation to be automatically discovered by the JAX-RS runtime
during a provider scanning phase.
Programmatically registered example. In JAX-RS application “MyApplication”, add exception mapper “MapperA” as a singleton. Now, Mapper A is programmatically registered in a JAX-RS runtime.
Automatic discovery example. Add
@Provider annotation to Mapper A, so that it can
be automatically discovered by the JAX-RS runtime during a provider scanning
Exception Mapping Mechanism
Exception mapping providers map a checked or runtime exception to an instance of
Response. When choosing an exception mapping provider to map an exception,
JAX-RS implementation (e.g. Jersey) use the provider whose generic type is the
nearest superclass of the exception. If two or more exception providers are
applicable, the one with the highest priority will be chosen. (Spec §4.4)
Mapping = nearest superclass + highest priority
For example, in our demo, 2 exception mappers are available:
MapperAfor mapping all exceptions of class
ExceptionAor its sub-classes
MapperA1for mapping all exceptions of class
ExceptionA1or its sub-classes, where
ExceptionA1is a child class of
and the following exceptions:
java.lang.Exception └── java.lang.RuntimeException └── io.mincong.demo.ExceptionA ├── io.mincong.demo.ExceptionA1 └── io.mincong.demo.ExceptionA2
The mapping will behave as the table below:
|Exception||Mapper A1||Mapper A||General Mapper|
Is is possible to have infinite loop when handling exception?
According to Spec §4.4 Exception Mapping Providers, JAX-RS implementations use a single exception mapper during the processing of a request and its corresponding response. So this should never happen.
A Failing Exception Mapper
What will happen if exception mapper throws an exception?
If an exception mapping provider throws an exception while creating a
then, then a server error (status code 500) response is returned to the client
(Spec §3.3.4 Exceptions).
We can verify it using the following resource method and exception mapper:
Verify using cUrl in terminal:
$ curl -i http://localhost:8080/failing HTTP/1.1 500 Internal Server Error Connection: close Content-Length: 0
We can see the response is 500. And the following stack trace can be observed in the server log:
Dec 03, 2018 9:46:47 PM org.glassfish.jersey.server.ServerRuntime$Responder mapException SEVERE: An exception was not mapped due to exception mapper failure. The HTTP 500 response will be returned. io.mincong.demo.FooException at io.mincong.demo.DemoResource.newFooException(DemoResource.java:38) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) ...
In this article, we learnt the definition of exception mapper, how to register it in JAX-RS application (programmatically or via annotation). We’ve also seen the matching mechanism (nearest-superclass). At the end, we verified that a failing provider is handled by JAX-RS implementation.
As usual, the source code is available for free on GitHub as mincong-h/jaxrs-2.x-demo. Feel free to download it and give it a try. Hope you enjoy this article, see you the next time!