users@jersey.java.net

Idea for new contribution -- a pattern for handling HTTP errors as Java exceptions?

From: Craig McClanahan <Craig.McClanahan_at_Sun.COM>
Date: Thu, 23 Oct 2008 17:22:03 -0700

In addition to the jersey-multipart stuff I contributed for creating and
consuming MIME multipart messages (see the "jersey-multipart" module in
the "contribs" directory), one of the other things I've been doing in my
"day job" work is defining a common "best practices" approach to use for
error handling in RESTful web services. We all know that JAX-RS (and
therefore Jersey) lets us create and consume HTTP messages with any
needed status code, but that still doesn't address two concerns:

* Ensuring that every developer on the team uses the same HTTP status code
  for the same "error" scenario, across multiple services (so that the
service APIs
  all look like they actually came from the same company :-).

* Passing error messages in a consistent format that the client can rely
on, and
  parse to good effect. For example, if your API offers a POST URI to
create a new
  resource, but several of the input fields in the entity have incorrect
or missing values,
  it would be nice to have a data structure that lets me include zero or
more error
  messages, optionally tagged to the corresponding field names (so that
a client side
  UI can present them nicely).

Does this sound like an interesting problem to anyone else?

The solution being developed by our team started from the notion that
Java developers are used to handling "exceptional" situations with ...
"exceptions" :-). Back in the Jersey 0.8 or so time frame, JAX-RS added
the concept of an ExceptionMapper, which basically acts like a
MessageBodyWriter when the resource method itself throws an exception.
So, maybe we can give the server side developer a set of well defined
exception classes (and corresponding exception mappers) that the
resource method can just throw, and let the library deal with setting
the correct status code and an entity containing the right message
infomation.

On the server side, your resource method code might look something like
this:

    @GET
    @Path("/customers/{id}")
    @Produces({"application/json","application/xml","text/xml"})
    public Customer find(@PathParam("id") String id) {
        // Finder method returns null if no such customer exists
        Customer cust = MyBusinessLogic.findCustomerById(id);
        if (cust != null) {
            return cust; // Use the provider for formatting this response
        } else {
            // NotFoundException is a RuntimeException so you do not need to
            // declare it in a throws clause, but you SHOULD javadoc it
            throw new NotFoundException("Customer '" + id + "' does not
exist");
        }
    }

The library would provide an exception mapper that enforces our "best
practice" policy to use an HTTP 404 "Not Found" status for this
condition, and package an appropriate message body containing the
message text, in a standard XML or JSON format.

For extra credit, I write client SDKs for our APIs as well. Why can't
we make it possible for the client developer to reason in exceptions
just like the server side developer can? That's actually pretty
straightforward, if you have a client side library that knows how to map
HTTP error responses back into an exception again.

Let's imagine a client side business logic class (based on
jersey-client) that abstracts calls to the above service with a nice
Java API. The code to call the server method described above might look
like:

    public class CustomerService {

        WebResource service = ...; // See jersey-client examples for
setting this up

        public Customer findCustomerById(String id) {
            try {
                return service.
                    path("customers/" + id).
                    accept("text/xml").
                    get(Customer.class);
            } catch (UniformInterfaceException e) {
                throw ErrorsHelper.asException(e.getResponse());
            }
        }

    }

where ErrorsHelper.asException analyzes the HTTP status of the response
and synthesizes an appropriate exception (in our case, turning a 404
into a NotFoundException) and grabs the relevant information out of the
response body (including the error message text).

So, does this sort of thing sound like something you might use? It
would be pretty straightforward to generalize what I've already got
working to make it fit in with Jersey (that's mostly a matter of a Maven
based build environment plus package name changes), if it sounds like
this would be generally useful.

Craig