Writing a Dropwizard JSON App

This article assumes some precursory knowledge of Dropwizard, a framework that has been invaluable when creating JSON oriented RESTful web services. Dropwizard is incredibly fast (I should insert a citation here, but metrics provided with the framework suggest there is neglible overhead due to itself). But maybe the most important aspect of the framework is that there is very little boilerplate to it (and we’re talking about Java here). It’s not as terse as some python microframeworks (I’m looking at you Flask and bottle.py), but I’ll compromise.

The one thing that Dropwizard doesn’t assume and that we need to add is making all responses in JSON. It is straightforward to make endpoints return JSON. The tricky part are exceptions. Mapping exceptions to the appropriate JSON response is what we’ll accomplish here. As a side note, Dropwizard isn’t alone in not implementing JSON exceptions by default. No framework that I know of does this (meteor might, but I’ve never used it, and meteor is a platform not a framework).

For the sake of convenience, I will inline Dropwizard’s Error Handling section and then I’ll go on to break it down.

If your resource class unintentionally throws an exception, Dropwizard will log that exception (including stack traces) and return a terse, safe text/plain 500 Internal Server Error response.

If your resource class needs to return an error to the client (e.g., the requested record doesn’t exist), you have two options: throw a subclass of Exception or restructure your method to return a Response.

If at all possible, prefer throwing Exception instances to returning Response objects.

If you throw a subclass of WebApplicationException jersey will map that to a defined response.

If you want more control, you can also declare JerseyProviders in your Environment to map Exceptions to certain responses by calling JerseyEnvironment#register(Object) with an implementation of javax.ws.rs.ext.ExceptionMapper. e.g. Your resource throws an InvalidArgumentException, but the response would be 400, bad request.

Several takeaways:

  • Dropwizard defaults to text reply (we want JSON)
  • Prefer throwing exceptions (ok, this is good, we don’t have to change APIs to add error handling)
  • We must define an ExceptionMapper to transform these exceptions into a JSON response

The correct solution to map expected exceptions into JSON is to define a class that implements ExceptionMapper<WebApplicationException>. This specific implementation is important because when the server detects an HTTP 404 Not Found, 405 Method Not Allowed, 415 Unsupported Media Type, etc, the server throws an instance of WebApplicationException. We want to catch these exceptions and turn them into JSON, otherwise Dropwizard would turn them into a plaintext respones.

The following is a class that has worked well in production.

public class WebExceptionMapper implements ExceptionMapper<WebApplicationException> {
    @Override
    public Response toResponse(final WebApplicationException e) {
        // If the message did not come with a status, we'll default to an internal
        // server error status.
        int status = e.getResponse() == null ? 500 : e.getResponse().getStatus();

        // Get a nice human readable message for our status code if the exception
        // doesn't already have a message
        final String msg = e.getMessage() == null ?
                HttpStatus.getMessage(status) : e.getMessage();

        // Create a JSON response with the provided hashmap
        return Response.status(status)
                .type(MediaType.APPLICATION_JSON_TYPE)
                .entity(new HashMap<String, String>() { {
                    put("error", msg);
                } }).build();
    }
}

To make this class effective in your application, add it to the application Jersey’s environment.

@Override
public void run(Configuration config, Environment env) {
    env.jersey().register(new WebExceptionMapper());
    /* snip */
}

Unexpected Errors

I alluded to it earlier, our curent solution works well for exceptions that we know exist in our program. In many server side programs there are exceptions that are infeasible to handle and so the only solution is to let the client know that something is wrong – our only requirement is that we want to let the user know with a JSON response and an HTTP 500 Internal Server Error. A great example of an unexpected error is if the database disconnected, a timeout ocurred, or maybe a rookie mistake was made and a null pointer was dereferenced

The solution to these problems is to add an ExceptionMapper for RuntimeException. Since these are unexpected exceptions, I often like verbose error messages that allow me to track down the problem and see if I can fix it. This involves capturing the stacktrace. For brevity, I won’t post an example, but it is safe to say it is similar to the example prior, except that a HTTP 500 status is always returned.

Authentication

If you use authentication within Dropwizard, the default response when the user fails to authenticate is an plain text response. Obviously, this does not fit in with our JSON app, so we override by defining our own custom UnauthorizedHandler and register it our AuthFactory. Here’s a simple implementation:

public class JsonUnauthorizedHandler implements UnauthorizedHandler {
    private static final String CHALLENGE_FORMAT = "%s realm=\"%s\"";

    @Override
    public Response buildResponse(String prefix, String realm) {
        // Create an HTTP 401 Unauthorized response with a JSON payload of a
        // human readable error
        return Response.status(Response.Status.UNAUTHORIZED)
                .header(HttpHeaders.WWW_AUTHENTICATE,
                    String.format(CHALLENGE_FORMAT, prefix, realm))
                .type(MediaType.APPLICATION_JSON_TYPE)
                .entity(ImmutableMap.of("error",
                    "Credentials are required to access this resource."))
                .build();
    }
}

Metrics

As an added bonus, it can be immensly helpful to add metrics to the various errors that can occur to provide a deeper insight. I highly recommed it just make sure these metrics are more meaningful than the ones that currently ship with Dropwizard. There are metrics on 4xx and 5xx out of the box.

Internals

A good eye would notice that an ExceptionMapper for RuntimeException could handle WebApplicationException, as WebApplicationException has RuntimeException as one of its ancestors. Jersey is able to determine what ExceptionMapper to use by examining the exception being thrown and what each mapper handles. It finds the closest implementation by looking at the inheritance hierarchy. Since WebApplicationException derives directly from RuntimeException, the distance is one. Jersey finds the ExceptionMapper with the minimum distance, hence why we can have both mappers and they don’t conflict with each other. If you’d like to take a look at how Jersey implements this, the source code is on Github.

Comments

If you'd like to leave a comment, please email [email protected]

2017-10-04 - Ash

Can you mention the full class path for HttpStatus that you use in the code? Thanks.

2017-10-04 - Nick

org.eclipse.jetty.http.HttpStatus.getMessage(status)

2017-10-04 - Antonis

With dropwizard version 0.9.2, it seems that if the resource is marked with the appropriate annotation for returning json (@Produces(MediaType.APPLICATION_JSON)) if a Runtime or WebApplicationException is thrown, Dropwizard returns the error in json format. Something like: HTTP/1.1 500 Internal Server Error Date: Thu, 31 Mar 2016 19:29:43 GMT Content-Type: application/json Content-Length: 110

{“code”:500,“message”:“There was an error processing your request. It has been logged (ID d729de9738156fbf).”} or HTTP/1.1 401 Unauthorized Date: Thu, 31 Mar 2016 19:32:22 GMT Content-Type: application/json Content-Length: 46

{“code”:401,“message”:“HTTP 401 Unauthorized”}

which is good enough in some cases.

2017-10-04 - Rags

Thanks for the Nice explanation, it really helped me!