I need some help with understanding how Websphere Liberty (18.0.0.1) handles exceptions thrown within a JAX-RS endpoint invocation. I'm using Liberty feature jaxrs-2.0
, so the implementation should be provided by WLP.
Now, my application has a POST HTTP endpoint accepting JSON payload and I'd like to provide a custom error messages for all the possible wrong client inputs.
Here's one case that works in a way I expected it:
application/xml
instead of application/json
ClientErrorException
thrown by the containerExceptionMapper<WebApplicationException>
to handle this exception (actually to handle all the web application exception, which I'm fine with)And here's the case not working for me:
application/json
, but with empty bodyjava.io.EOFException: No content to map to Object due to end of input
- yeah, that looks accurateEOFException
into some kind of WebApplicationException
(which I could handle easily), WLP is wrapping the exception issue into JaxRsRuntimeException
A couple of points here:
ExceptionMapper<JaxRsRuntimeException>
because that exception is not a part of JAX-RS 2.0 spec and I'd have to provide the import to JaxRsRuntimeException and wire the application with some Liberty-specific library.ExceptionMapper<RuntimeException>
and string check if it finds exception of classname 'JaxRsRuntimeException' and then handle it. But that just doesn't seem right to me. So, is that a WLP design not to give me a WebApplicationException in this case? What would be the elegant solution to handle this scenario?
Thanks
EDIT: Added some parts of source code.
REST endpoint and resource method:
@Path("/books")
public class BookEndpoint {
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response createBook(Book book, @Context UriInfo uriInfo) {
bookDao.create(book);
UriBuilder builder = uriInfo.getAbsolutePathBuilder();
builder.path(Integer.toString(book.getId()));
return Response.created(builder.build()).entity(book).build();
}
}
Entity with JAXB annotations:
@XmlRootElement
public class Book {
private int id;
private String title;
// getters, setters
}
Exception stack trace:
com.ibm.ws.jaxrs20.JaxRsRuntimeException: java.io.EOFException: No content to map to Object duto end of input
at org.apache.cxf.jaxrs.utils.JAXRSUtils.toJaxRsRuntimeException(JAXRSUtils.java:1928)
at [internal classes]
at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:201)
at [internal classes]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.EOFException: No content to map to Object duto end of input
at org.codehaus.jackson.map.ObjectMapper._initForReading(ObjectMapper.java:2775)
at [internal classes]
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBodyReader(JAXRSUtils.java:1413)
at [internal classes]
... 48 more
This is the expected behavior based on Section 3.3.4 (and 4.5.1) of the JAX-RS 2.0 Spec. These sections describe how exceptions from JAX-RS resources and providers are handled - in short:
WebApplicationException
, then it will automatically mapped to a Response
.ExceptionMapper
registered that can handle the thrown exception, then that will be used to generate the response.ServletException
must be passed to the web container.The JaxRsRuntimeException
is used to satisfy step 4.
In this scenario the built-in JSON provider (based on Jackson 1.X) is throwing the EOFException
. Since there are no exception mappers for the EOFException (or any of it's superclasses), it is ultimately mapped to a ServletException
by way of the JaxRsRuntimeException
.
In order for an application to handle this scenario, there are a few different options:
ExceptionMapper
that is specific to this exception type (EOFException
or any of it's superclasses - i.e. IOException
). You should not need to register a mapper for JaxRsRuntimeException
as that exception is only used internally in Liberty - and should not be mapped. If you are seeing the JaxRsRuntimeException passed to an ExceptionMapper
, then you should open a support case with IBM, as this is likely a bug.With an ExceptionMapper<EOFException>
you can return a specific response whenever an EOFException
is thrown from a provider or resource.
MessageBodyReader
that will convert JSON to objects (using Jackson or any other JSON serialization code) but that will handle empty message bodies in the way you want - for example, converting it to null
or using some kind of default object instance. Because user-registered providers take priority over built-in providers, this MBR would be used instead of Liberty's Jackson-based MBR.This approach definitely gives you more control over how the data is deserialized as well as the exception handling.
Register a ContainerRequestFilter
provider that will abort when the message body is empty. Here is an example:
@Provider
public class EmptyBodyCheckFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext crc) throws IOException {
if (crc.getEntityStream().available() < 1) {
crc.abortWith(Response.status(400).entity("Invalid request - empty message body").build());
}
}
}
I've successfully tested options 1 and 3 using the WebSphere Liberty May 2018 Beta. I haven't personally tested option 2 for this scenario, but based on using custom MBRs in the past, this should work.
One thing to keep in mind is that when Liberty GAs the jaxrs-2.1
feature, it will use JSONB as the built-in provider for serializing/deserializing JSON instead of Jackson. I tested your scenario using JAX-RS 2.1 (also in the May Beta) and instead of an EOFException
, the JSONB code throws a NoSuchElementException
. If you think you might move to JAX-RS 2.1, then I would suggest option 2 or 3. Option 1 would require that you create a new ExceptionMapper
for JAX-RS 2.1.
Hope this helps,
Andy
Not a direct answert on "why WLP wrap the exception ..etc" but maybe add an exception interceptor as you did but on"ExceptionMapper<Exception>"
and recusrsively iterate on the "causes" to check if java.io.EOFException
is one of those...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With