19

I am using Jersey 2.10 with Jackson serialization/deserialization feature in my REST API.

My idea is to make my REST API to always return a standard JSON error response. For that I have ExceptionMapper classes that build proper json error responses for any exception being thrown in the Jersey application. I also have a jsp which produces the same kind of JSON response, which I registered as error-page in the web.xml that covers all the errors that could come before Jersey being loaded.

But there is one case in which neither my Exception mappers nor my json producing jsp are working, that is when sending a bad formed json to a POST REST endpoint which just returns the following message:

HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Content-Type: text/plain
Content-Length: 210
Date: Tue, 24 Jun 2014 22:14:11 GMT
Connection: close

Can not deserialize instance of com.example.rest.User[] out of START_OBJECT token
 at [Source: org.glassfish.jersey.message.internal.EntityInputStream@1dcccac; line: 1, column: 1]

How can I make Jersey to return my custom error response instead of this?

UPDATE:

Based on the answer by @Lucasz, I did more research and found that there are two Exception mappers defined inside the package com.fasterxml.jackson.jaxrs.base (https://github.com/FasterXML/jackson-jaxrs-providers/tree/master/base/src/main/java/com/fasterxml/jackson/jaxrs/base) JsonMappingExceptionMapper and JsonParseExceptionMapper that seem to be shadowing my custom mappers.

How can I unregister those mappers?

This is how I am currently registering the mappers:

@ApplicationPath("/")
public class MyApp extends ResourceConfig{
    public SyntheticAPIApp() {
        packages("com.example.resource", "com.example.mapper");
        register(org.glassfish.jersey.jackson.JacksonFeature.class);
    }
}
raspacorp
  • 5,037
  • 11
  • 39
  • 51
  • Normally an invalid JSON triggers a 500 Internal Server Error response. It looks like some exception mapper worked and converted the JsonParseExcpetion to a 400 response with the body taken from the exception message. – Lukasz Wiktor Jun 26 '14 at 07:21
  • But I don't have any exception mapper that produces a text/plain response, it seems that someone is intercepting that exception because I don't even see it in the logs – raspacorp Jun 26 '14 at 17:34

6 Answers6

35

I tested it with an exception mapper like below:

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.core.JsonProcessingException;

@Provider
public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException>{

        public static class Error {
            public String key;
            public String message;
        }

        @Override
        public Response toResponse(JsonProcessingException exception) {
            Error error = new Error();
            error.key = "bad-json";
            error.message = exception.getMessage();
            return Response.status(Status.BAD_REQUEST).entity(error).build();
        }
}

and it worked.


Update: changed JsonParseException to JsonProcessingException (more general)


Update2: In order to avoid registering the unwanted mappers replace

register(org.glassfish.jersey.jackson.JacksonFeature.class);

with

register(com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.class);

Look at the source code of JacksonFeature and you'll understand what's happening.

Lukasz Wiktor
  • 19,644
  • 5
  • 69
  • 82
  • What version of Jersey and jackson provider are you using? I am using jersey 2.10 with jersey-media-json-jackson library. My method is receiving an array of User objects as input for example it will accept [{"name":"John",...}] but will fail as epected with {"name":"John",...}, is your method receiving an array too? – raspacorp Jun 26 '14 at 17:32
  • @raspacorp I tested it with jersey 2.9.1 and jackson-jaxrs-json-provider 2.3.3 – Lukasz Wiktor Jun 26 '14 at 18:22
  • @raspacorp I tried to send a single object to a method accepting a list and my mapper didn't work but the reason was that a different exception was thrown (JsonMappingException). I changed my mapper to accept JsonProcessingException (base class for JsonParseException and JsonMappingException) and then it worked correctly. – Lukasz Wiktor Jun 27 '14 at 08:03
  • But did you receive the same message that I am getting when your mapper didn't work? In my case I have multiple exception mappers one of them is a generic java.lang.Exception which is supposed to capture every exception that is not matched to a specific mapper. But in this case it seems that somebody is generating that uggly message before my generic mapper and therefore shadowing it – raspacorp Jun 27 '14 at 19:40
  • No, I received a standard html error page generated by the server when an exception is unhandled. Try to increase log level to maxiumum details - there should appear an entry listing all resources and providers including exception mappers. – Lukasz Wiktor Jun 27 '14 at 20:02
  • I am using sl4j and log4j with TRACE level, but no information is shown about the mapper, I think the behavior is different because I am using the jersey-media-json-jackson which seems to have a custom mapper for this case – raspacorp Jun 30 '14 at 14:25
  • as I thought, the jackson provider has its own mappers, look at https://github.com/FasterXML/jackson-jaxrs-json-provider/tree/master/src/main/java/com/fasterxml/jackson/jaxrs/json which are shadowing mine, If I create mine I think the results will be unpredictable, sometimes it will pick mine and others not. Didn't that happen to you in your example? – raspacorp Jul 01 '14 at 19:13
  • It looks like you caught the culprit! However, I didn't come across it in my example. How do you register resources and providers in your application? I scan classes only from my own packages and I register the JacksonJsonProvider explicitly - probably that's why the mapper from com.fasterxml... doesn't come into play. And about logs - jersey uses java.util.logging so you need to configure it separately or use a jul to slf4j bridge. But for sure there is a log entry listing all registered providers and the unwanted one should be also on that list. – Lukasz Wiktor Jul 01 '14 at 20:55
  • yes I will try to set the log as you recommend. I am registering providers from packages scan with packages() method in my ResourceConfig subclass and registering JacksonFeature using the register method inside that same class – raspacorp Jul 01 '14 at 22:05
  • @raspacorp I've updated my answer to hopefully final solution. – Lukasz Wiktor Jul 05 '14 at 19:21
  • 1
    Thank you @Lukasz actually we discovered that maybe at the same time, I was going to put that as my final solution but after looking at your update it is not necessary, I will only add that I used the same line as in the JacksonFeature class for registering the provider: register(JacksonJaxbJsonProvider.class, MessageBodyReader.class, MessageBodyWriter.class); – raspacorp Jul 07 '14 at 20:50
  • 1
    I had to catch `JsonParseException`, for some reason `JsonProcessingException` wasnt catching this in the exception mapper... – vikingsteve Aug 29 '17 at 09:00
  • replacing `org.glassfish.jersey.jackson.JacksonFeature.class` with `com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider` is all we require, also we have to remove the dependency `org.glassfish.jersey.media:jersey-media-json-jackson` from the classpath – Prasanth Rajendran Mar 18 '21 at 18:42
4

Starting with Jersey 2.26 (1, 2) it should be enough to annotate the custom exception mapper with a sufficiently high Priority (high here meaning a low, strictly positive number). To override the “default” mappers provided by org.glassfish.jersey.media:jersey-media-json-jackson (to register(JacksonFeature.class)) we only provide these two custom mappers:

@Provider
@Priority(1)
public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
  /* ... */
}
@Provider
@Priority(1)
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
  /* ... */
}

Unfortunately JAX-RS 2 Spec disregards priorities and only states:

When choosing an exception mapping provider to map an exception, an implementation MUST use the provider whose generic type is the nearest superclass of the exception.

Not registering JacksonFeature.class and registering JacksonJaxbJsonProvider.class instead as mentioned in another answer did not lead to consistent results.

René
  • 904
  • 13
  • 26
2

I had the same problem, and the previous answer led me to the solution, but was not forking for me current Jersey (2.22). At first, I needed to use the org.glassfish.jersey.spi.ExtendedExceptionMapper like described in https://jersey.java.net/documentation/latest/representations.html.

Furthermore, Jersey is checking for an exception mapper, which is as close as possible to the thrown exception (from org.glassfish.jersey.internal.ExceptionMapperFactory):

for (final ExceptionMapperType mapperType : exceptionMapperTypes) {
        final int d = distance(type, mapperType.exceptionType);
        if (d >= 0 && d <= minDistance) {
            final ExceptionMapper<T> candidate = mapperType.mapper.getService();

            if (isPreferredCandidate(exceptionInstance, candidate, d == minDistance)) {
                mapper = candidate;
                minDistance = d;
                if (d == 0) {
                    // slight optimization: if the distance is 0, it is already the best case, so we can exit
                    return mapper;
                }
            }
        }
    }

Therefore I needed to map exactly the exception and not a more general exception.

In the end, my provider looks as follows:

@Provider
public final class JsonParseExceptionExceptionHandler implements ExtendedExceptionMapper<JsonParseException> {
    @Override
    public Response toResponse(final JsonParseException exception) {
        exception.printStackTrace();
        return Response.status(Response.Status.BAD_REQUEST).entity("JSON nicht in korrektem Format.").build();
    }

    @Override
    public boolean isMappable(final JsonParseException arg0) {
        return true;
    }
}
David Georg Reichelt
  • 963
  • 1
  • 15
  • 36
  • This will work unpredictably, as stated in http://stackoverflow.com/questions/15989212/overriding-included-provider-in-jersey The solution is to get rid of bridge jersey-media-json-jackson and register `com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.class` manually, like any other provider. – genobis Apr 28 '16 at 13:23
2

I used "jackson-jaxrs-json-provider 2.8.8" and JAX-RS 2.0

Application class - you needs to register your ExceptionMapper implementation class:

@ApplicationPath("pathApplication")
public class ApplicationConfiguration extends Application{


   @Override
   public Set<Class<?>> getClasses() {

      Set<Class<?>> resources = new HashSet<>();
      resources.add(YourJAXRSClass.class);
      resources.add(JsonJacksonEM.class); //ExceptionMapper class implementation
      //others resources that you need...
      return resources; 

   }


}

ExceptionMapper class implementation:

@Provider
public class JsonJacksonEM implements ExceptionMapper<JsonParseException>{


   @Override
   public Response toResponse(JsonParseException exception) {
      //you can return a Response in the way that you want!
      return Response.ok(new YourObject()).build();
   }


}
heronsanches
  • 524
  • 7
  • 12
0

I had the same problem and solve overriding the ExceptionMapper. Perfect! One extra thing that I needed to do and were not understanding 100% was how to override the JacksonProvider for my application (I don't know if it was related to Jersey's version that I was using - 2.19). Here's my web.xml part that overrides it:

<init-param>
    <param-name>jersey.config.server.provider.classnames</param-name>
       <param-value>
          com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
       </param-value>
</init-param>
Mateus Costa
  • 103
  • 3
  • 5
0

I have faced this issue recently, and the solution of registering com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider doesn't helped much. When I dig deeper I came to know that Jersey by default org.glassfish.jersey.jackson.JacksonFeature is registered by Jersey, if the dependency jersey-media-json-jackson even without the explicit declaration of registering it(Don't sure from which version the auto register is implemented, I guess atleast from Jersey 2.29) present in the classpath. The JacksonFeature inturn registers JsonParseExceptionMapper and JsonMappingExceptionMapper automatically. Because of these default JSON exception mappers, all JSON related exceptions are not redirected to the custom exception mapper

Fortunately, Jersey 2.29.1 added support for registering JacksonFeature without the exception handlers. link feature request link, code changes.

@Provider
public class ApplicationConfig extends Application {
   @Override
   public Set<Object> getSingletons() {
      Set<Object> singletons = super.getSingletons();
      singletons.add(JacksonFeature.withoutExceptionMappers());
      return singletons;
   }
}

The above code snippet will override the default JacksonFeature registered by Jersey. By doing so, all JSON-related exceptions will be redirected to custom exception mappers present in the application.

Prasanth Rajendran
  • 4,570
  • 2
  • 42
  • 59