title: Handling custom error response in JAX-RS 2.0 client library
body: <p>I am starting to use the new client API library in JAX-RS and really loving it so far.  I have found one thing I cannot figure out however.  The API I am using has a custom error message format that looks like this for example:</p>
  
  <pre><code>{
      "code": 400,
      "message": "This is a message which describes why there was a code 400."
  } 
  </code></pre>
  
  <p>It returns 400 as the status code but also includes a descriptive error message to tell you what you did wrong.</p>
  
  <p>However the JAX-RS 2.0 client is re-mapping the 400 status into something generic and I lose the good error message.  It correctly maps it to a BadRequestException, but with a generic "HTTP 400 Bad Request" message.</p>
  
  <pre><code>javax.ws.rs.BadRequestException: HTTP 400 Bad Request
      at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:908)
      at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:770)
      at org.glassfish.jersey.client.JerseyInvocation.access$500(JerseyInvocation.java:90)
      at org.glassfish.jersey.client.JerseyInvocation$2.call(JerseyInvocation.java:671)
      at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
      at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
      at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
      at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:424)
      at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:667)
      at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:396)
      at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:296)
  </code></pre>
  
  <p>Is there some sort of interceptor or custom error handler that can be injected so that I get access to the real error message.  I've been looking through documentation but can't see any way of doing it.</p>
  
  <p>I am using Jersey right now, but I tried this using CXF and got the same result.  Here is what the code looks like.      </p>
  
  <pre><code>Client client = ClientBuilder.newClient().register(JacksonFeature.class).register(GzipInterceptor.class);
  WebTarget target = client.target("https://somesite.com").path("/api/test");
  Invocation.Builder builder = target.request()
                                     .header("some_header", value)
                                     .accept(MediaType.APPLICATION_JSON_TYPE)
                                     .acceptEncoding("gzip");
  MyEntity entity = builder.get(MyEntity.class);
  </code></pre>
  
  <p>UPDATE:</p>
  
  <p>I implemented the solution listed in the comment below.  It is slightly different since the classes have changed a bit in the JAX-RS 2.0 client API.  I still think it is wrong that the default behavior is to give a generic error message and discard the real one.  I understand why it wouldn't parse my error object, but the un-parsed version should have been returned.  I end up having the replicate exception mapping that the library already does.</p>
  
  <p>Thanks for the help.</p>
  
  <p>Here is my filter class:</p>
  
  <pre><code>@Provider
  public class ErrorResponseFilter implements ClientResponseFilter {
  
      private static ObjectMapper _MAPPER = new ObjectMapper();
  
      @Override
      public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
          // for non-200 response, deal with the custom error messages
          if (responseContext.getStatus() != Response.Status.OK.getStatusCode()) {
              if (responseContext.hasEntity()) {
                  // get the "real" error message
                  ErrorResponse error = _MAPPER.readValue(responseContext.getEntityStream(), ErrorResponse.class);
                  String message = error.getMessage();
  
                  Response.Status status = Response.Status.fromStatusCode(responseContext.getStatus());
                  WebApplicationException webAppException;
                  switch (status) {
                      case BAD_REQUEST:
                          webAppException = new BadRequestException(message);
                          break;
                      case UNAUTHORIZED:
                          webAppException = new NotAuthorizedException(message);
                          break;
                      case FORBIDDEN:
                          webAppException = new ForbiddenException(message);
                          break;
                      case NOT_FOUND:
                          webAppException = new NotFoundException(message);
                          break;
                      case METHOD_NOT_ALLOWED:
                          webAppException = new NotAllowedException(message);
                          break;
                      case NOT_ACCEPTABLE:
                          webAppException = new NotAcceptableException(message);
                          break;
                      case UNSUPPORTED_MEDIA_TYPE:
                          webAppException = new NotSupportedException(message);
                          break;
                      case INTERNAL_SERVER_ERROR:
                          webAppException = new InternalServerErrorException(message);
                          break;
                      case SERVICE_UNAVAILABLE:
                          webAppException = new ServiceUnavailableException(message);
                          break;
                      default:
                          webAppException = new WebApplicationException(message);
                  }
  
                  throw webAppException;
              }
          }
      }
  }
  </code></pre>