[java] JAX-RS / Jersey how to customize error handling?

I'm learning JAX-RS (aka, JSR-311) using Jersey. I've successfuly created a Root Resource and am playing around with parameters:

@Path("/hello")
public class HelloWorldResource {

    @GET
    @Produces("text/html")
    public String get(
        @QueryParam("name") String name,
        @QueryParam("birthDate") Date birthDate) {

         // Return a greeting with the name and age
    }
}

This works great, and handles any format in the current locale which is understood by the Date(String) constructor (like YYYY/mm/dd and mm/dd/YYYY). But if I supply a value which is invalid or not understood, I get a 404 response.

For example:

GET /hello?name=Mark&birthDate=X

404 Not Found

How can I customize this behavior? Maybe a different response code (probably "400 Bad Request")? What about logging an error? Maybe add a description of the problem ("bad date format") in a custom header to aid troubleshooting? Or return a whole Error response with details, along with a 5xx status code?

This question is related to java rest error-handling jersey jax-rs

The answer is


Jersey throws an com.sun.jersey.api.ParamException when it fails to unmarshall the parameters so one solution is to create an ExceptionMapper that handles these types of exceptions:

@Provider
public class ParamExceptionMapper implements ExceptionMapper<ParamException> {
    @Override
    public Response toResponse(ParamException exception) {
        return Response.status(Status.BAD_REQUEST).entity(exception.getParameterName() + " incorrect type").build();
    }
}

This is the correct behavior actually. Jersey will try to find a handler for your input and will try to construct an object from the provided input. In this case it will try to create a new Date object with the value X provided to the constructor. Since this is an invalid date, by convention Jersey will return 404.

What you can do is rewrite and put birth date as a String, then try to parse and if you don't get what you want, you're free to throw any exception you want by any of the exception mapping mechanisms (there are several).


I was facing the same issue.

I wanted to catch all the errors at a central place and transform them.

Following is the code for how I handled it.

Create the following class which implements ExceptionMapper and add @Provider annotation on this class. This will handle all the exceptions.

Override toResponse method and return the Response object populated with customised data.

//ExceptionMapperProvider.java
/**
 * exception thrown by restful endpoints will be caught and transformed here
 * so that client gets a proper error message
 */
@Provider
public class ExceptionMapperProvider implements ExceptionMapper<Throwable> {
    private final ErrorTransformer errorTransformer = new ErrorTransformer();

    public ExceptionMapperProvider() {

    }

    @Override
    public Response toResponse(Throwable throwable) {
        //transforming the error using the custom logic of ErrorTransformer 
        final ServiceError errorResponse = errorTransformer.getErrorResponse(throwable);
        final ResponseBuilder responseBuilder = Response.status(errorResponse.getStatus());

        if (errorResponse.getBody().isPresent()) {
            responseBuilder.type(MediaType.APPLICATION_JSON_TYPE);
            responseBuilder.entity(errorResponse.getBody().get());
        }

        for (Map.Entry<String, String> header : errorResponse.getHeaders().entrySet()) {
            responseBuilder.header(header.getKey(), header.getValue());
        }

        return responseBuilder.build();
    }
}

// ErrorTransformer.java
/**
 * Error transformation logic
 */
public class ErrorTransformer {
    public ServiceError getErrorResponse(Throwable throwable) {
        ServiceError serviceError = new ServiceError();
        //add you logic here
        serviceError.setStatus(getStatus(throwable));
        serviceError.setBody(getBody(throwable));
        serviceError.setHeaders(getHeaders(throwable));

    }
    private String getStatus(Throwable throwable) {
        //your logic
    }
    private Optional<String> getBody(Throwable throwable) {
        //your logic
    }
    private Map<String, String> getHeaders(Throwable throwable) {
        //your logic
    }
}

//ServiceError.java
/**
 * error data holder
 */
public class ServiceError {
    private int status;
    private Map<String, String> headers;
    private Optional<String> body;
    //setters and getters
}

You could also write a reusable class for QueryParam-annotated variables

public class DateParam {
  private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

  private Calendar date;

  public DateParam(String in) throws WebApplicationException {
    try {
      date = Calendar.getInstance();
      date.setTime(format.parse(in));
    }
    catch (ParseException exception) {
      throw new WebApplicationException(400);
    }
  }
  public Calendar getDate() {
    return date;
  }
  public String format() {
    return format.format(value.getTime());
  }
}

then use it like this:

private @QueryParam("from") DateParam startDateParam;
private @QueryParam("to") DateParam endDateParam;
// ...
startDateParam.getDate();

Although the error handling is trivial in this case (throwing a 400 response), using this class allows you to factor-out parameter handling in general which might include logging etc.


I too like StaxMan would probably implement that QueryParam as a String, then handle the conversion, rethrowing as necessary.

If the locale specific behavior is the desired and expected behavior, you would use the following to return the 400 BAD REQUEST error:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

See the JavaDoc for javax.ws.rs.core.Response.Status for more options.


@Provider
public class BadURIExceptionMapper implements ExceptionMapper<NotFoundException> {

public Response toResponse(NotFoundException exception){

    return Response.status(Response.Status.NOT_FOUND).
    entity(new ErrorResponse(exception.getClass().toString(),
                exception.getMessage()) ).
    build();
}
}

Create above class. This will handle 404 (NotFoundException) and here in toResponse method you can give your custom response. Similarly there are ParamException etc. which you would need to map to provide customized responses.


Just as an extension to @Steven Lavine answer in case you want to open the browser login window. I found it hard to properly return the Response (MDN HTTP Authentication) from the Filter in case that the user wasn't authenticated yet

This helped me to build the Response to force browser login, note the additional modification of the headers. This will set the status code to 401 and set the header that causes the browser to open the username/password dialog.

// The extended Exception class
public class NotLoggedInException extends WebApplicationException {
  public NotLoggedInException(String message) {
    super(Response.status(Response.Status.UNAUTHORIZED)
      .entity(message)
      .type(MediaType.TEXT_PLAIN)
      .header("WWW-Authenticate", "Basic realm=SecuredApp").build()); 
  }
}

// Usage in the Filter
if(headers.get("Authorization") == null) { throw new NotLoggedInException("Not logged in"); }

@QueryParam documentation says

" The type T of the annotated parameter, field or property must either:

1) Be a primitive type
2) Have a constructor that accepts a single String argument
3) Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String))
4) Have a registered implementation of javax.ws.rs.ext.ParamConverterProvider JAX-RS extension SPI that returns a javax.ws.rs.ext.ParamConverter instance capable of a "from string" conversion for the type.
5) Be List, Set or SortedSet, where T satisfies 2, 3 or 4 above. The resulting collection is read-only. "

If you want to control what response goes to user when query parameter in String form can't be converted to your type T, you can throw WebApplicationException. Dropwizard comes with following *Param classes you can use for your needs.

BooleanParam, DateTimeParam, IntParam, LongParam, LocalDateParam, NonEmptyStringParam, UUIDParam. See https://github.com/dropwizard/dropwizard/tree/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/params

If you need Joda DateTime, just use Dropwizard DateTimeParam.

If the above list does not suit your needs, define your own by extending AbstractParam. Override parse method. If you need control over error response body, override error method.

Good article from Coda Hale on this is at http://codahale.com/what-makes-jersey-interesting-parameter-classes/

import io.dropwizard.jersey.params.AbstractParam;

import java.util.Date;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

public class DateParam extends AbstractParam<Date> {

    public DateParam(String input) {
        super(input);
    }

    @Override
    protected Date parse(String input) throws Exception {
        return new Date(input);
    }

    @Override
    protected Response error(String input, Exception e) {
        // customize response body if you like here by specifying entity
        return Response.status(Status.BAD_REQUEST).build();
    }
}

Date(String arg) constructor is deprecated. I would use Java 8 date classes if you are on Java 8. Otherwise joda date time is recommended.


Approach 1: By extending WebApplicationException class

Create new exception by extending WebApplicationException

public class RestException extends WebApplicationException {

         private static final long serialVersionUID = 1L;

         public RestException(String message, Status status) {
         super(Response.status(status).entity(message).type(MediaType.TEXT_PLAIN).build());
         }
}

Now throw 'RestException' whenever required.

public static Employee getEmployee(int id) {

         Employee emp = employees.get(id);

         if (emp == null) {
                 throw new RestException("Employee with id " + id + " not exist", Status.NOT_FOUND);
         }
         return emp;
}

You can see complete application at this link.

Approach 2: Implement ExceptionMapper

Following mapper handles exception of type 'DataNotFoundException'

@Provider
public class DataNotFoundExceptionMapper implements
        ExceptionMapper<DataNotFoundException> {

    @Override
    public Response toResponse(DataNotFoundException ex) {
        ErrorMessage model = new ErrorMessage(ex.getErrorCode(),
                ex.getMessage());
        return Response.status(Status.NOT_FOUND).entity(model).build();
    }

}

You can see complete application at this link.


One obvious solution: take in a String, convert to Date yourself. That way you can define format you want, catch exceptions and either re-throw or customize error being sent. For parsing, SimpleDateFormat should work fine.

I am sure there are ways to hook handlers for data types too, but perhaps little bit of simple code is all you need in this case.


Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to rest

Access blocked by CORS policy: Response to preflight request doesn't pass access control check Returning data from Axios API Access Control Origin Header error using Axios in React Web throwing error in Chrome JSON parse error: Can not construct instance of java.time.LocalDate: no String-argument constructor/factory method to deserialize from String value How to send json data in POST request using C# How to enable CORS in ASP.net Core WebAPI RestClientException: Could not extract response. no suitable HttpMessageConverter found REST API - Use the "Accept: application/json" HTTP Header 'Field required a bean of type that could not be found.' error spring restful API using mongodb MultipartException: Current request is not a multipart request

Examples related to error-handling

must declare a named package eclipse because this compilation unit is associated to the named module Error:Failed to open zip file. Gradle's dependency cache may be corrupt What does 'index 0 is out of bounds for axis 0 with size 0' mean? What's the source of Error: getaddrinfo EAI_AGAIN? Error handling with try and catch in Laravel What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean? Raise error in a Bash script Javascript Uncaught TypeError: Cannot read property '0' of undefined Multiple values in single-value context IndexError: too many indices for array

Examples related to jersey

How to download a file using a Java REST service and a data stream File upload along with other object in Jersey restful web service MessageBodyWriter not found for media type=application/json org.glassfish.jersey.servlet.ServletContainer ClassNotFoundException How to add Headers on RESTful call using Jersey Client API java.lang.ClassNotFoundException: com.sun.jersey.spi.container.servlet.ServletContainer Dependency injection with Jersey 2.0 How to send and receive JSON data from a restful webservice using Jersey API Jersey client: How to add a list as query parameter @POST in RESTful web service

Examples related to jax-rs

Java 8 LocalDate Jackson format File upload along with other object in Jersey restful web service Best practice for REST token-based authentication with JAX-RS and Jersey MessageBodyWriter not found for media type=application/json Can not deserialize instance of java.util.ArrayList out of START_OBJECT token Read response body in JAX-RS client from a post request What is the difference between JAX-RS and JAX-WS? javax.xml.bind.JAXBException: Class *** nor any of its super class is known to this context When to use @QueryParam vs @PathParam How to set up JAX-RS Application using annotations only (no web.xml)?