Writing a Dropwizard JSON App
Published on:Table of Contents
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]
org.eclipse.jetty.http.HttpStatus.getMessage(status)
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.
Thanks for the Nice explanation, it really helped me!
Can you mention the full class path for HttpStatus that you use in the code? Thanks.