The dropwizard-core module provides you with everything you’ll need for most of your applications.
It includes:
Dropwizard consists mostly of glue code to automatically connect and configure these components.
In general, we recommend you separate your projects into three Maven modules: project-api, project-client, and project-application.
project-api should contain your Representations; project-client should use those classes and an HTTP client to implement a full-fledged client for your application, and project-application should provide the actual application implementation, including Resources.
Our applications tend to look like this:
The main entry point into a Dropwizard application is, unsurprisingly, the Application class. Each Application has a name, which is mostly used to render the command-line interface. In the constructor of your Application you can add Bundles and Commands to your application.
Dropwizard provides a number of built-in configuration parameters. They are well documented in the example project’s configuration.
Each Application subclass has a single type parameter: that of its matching Configuration subclass. These are usually at the root of your application’s main package. For example, your User application would have two classes: UserApplicationConfiguration, extending Configuration, and UserApplication, extending Application<UserApplicationConfiguration>.
When your application runs Configured Commands like the server command, Dropwizard parses the provided YAML configuration file and builds an instance of your application’s configuration class by mapping YAML field names to object field names.
Note
If your configuration file doesn’t end in .yml or .yaml, Dropwizard tries to parse it as a JSON file.
To keep your configuration file and class manageable, we recommend grouping related configuration parameters into independent configuration classes. If your application requires a set of configuration parameters in order to connect to a message queue, for example, we recommend that you create a new MessageQueueFactory class:
public class MessageQueueFactory {
@NotEmpty
private String host;
@Min(1)
@Max(65535)
private int port = 5672;
@JsonProperty
public String getHost() {
return host;
}
@JsonProperty
public void setHost(String host) {
this.host = host;
}
@JsonProperty
public int getPort() {
return port;
}
@JsonProperty
public void setPort(int port) {
this.port = port;
}
public MessageQueueClient build(Environment environment) {
MessageQueueClient client = new MessageQueueClient(getHost(), getPort());
environment.lifecycle().manage(new Managed() {
@Override
public void start() {
}
@Override
public void stop() {
client.close();
}
});
return client;
}
}
In this example our factory will automatically tie our MessageQueueClient connection to the lifecycle of our application’s Environment.
Your main Configuration subclass can then include this as a member field:
public class ExampleConfiguration extends Configuration {
@Valid
@NotNull
private MessageQueueFactory messageQueue = new MessageQueueFactory();
@JsonProperty("messageQueue")
public MessageQueueFactory getMessageQueueFactory() {
return messageQueue;
}
@JsonProperty("messageQueue")
public void setMessageQueueFactory(MessageQueueFactory factory) {
this.messageQueue = factory;
}
}
And your Application subclass can then use your factory to directly construct a client for the message queue:
public void run(ExampleConfiguration configuration,
Environment environment) {
MessageQueueClient messageQueue = configuration.getMessageQueueFactory().build(environment);
}
Then, in your application’s YAML file, you can use a nested messageQueue field:
messageQueue:
host: mq.example.com
port: 5673
The @NotNull, @NotEmpty, @Min, @Max, and @Valid annotations are part of Dropwizard Validation functionality. If your YAML configuration file’s messageQueue.host field was missing (or was a blank string), Dropwizard would refuse to start and would output an error message describing the issues.
Once your application has parsed the YAML file and constructed its Configuration instance, Dropwizard then calls your Application subclass to initialize your application’s Environment.
Note
You can override configuration settings by passing special Java system properties when starting your application. Overrides must start with prefix dw., followed by the path to the configuration value being overridden.
For example, to override the Logging level, you could start your application like this:
java -Ddw.logging.level=DEBUG server my-config.json
This will work even if the configuration setting in question does not exist in your config file, in which case it will get added.
You can override configuration settings in arrays of objects like this:
java -Ddw.server.applicationConnectors[0].port=9090 server my-config.json
You can override configuration settings in maps like this:
java -Ddw.database.properties.hibernate.hbm2ddl.auto=none server my-config.json
You can also override a configuration setting that is an array of strings by using the ‘,’ character as an array element separator. For example, to override a configuration setting myapp.myserver.hosts that is an array of strings in the configuration, you could start your service like this: java -Ddw.myapp.myserver.hosts=server1,server2,server3 server my-config.json
If you need to use the ‘,’ character in one of the values, you can escape it by using ‘,’ instead.
The array override facility only handles configuration elements that are arrays of simple strings. Also, the setting in question must already exist in your configuration file as an array; this mechanism will not work if the configuration key being overridden does not exist in your configuration file. If it does not exist or is not an array setting, it will get added as a simple string setting, including the ‘,’ characters as part of the string.
The dropwizard-configuration module also provides the capabilities to substitute configuration settings with the value of environment variables using a SubstitutingSourceProvider and EnvironmentVariableSubstitutor.
public class MyApplication extends Application<MyConfiguration> {
// [...]
@Override
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
// Enable variable substitution with environment variables
bootstrap.setConfigurationSourceProvider(
new SubstitutingSourceProvider(bootstrap.getConfigurationSourceProvider(),
new EnvironmentVariableSubstitutor(false)
)
);
}
// [...]
}
The configuration settings which should be substituted need to be explicitly written in the configuration file and follow the substitution rules of StrSubstitutor from the Apache Commons Lang library.
mySetting: ${DW_MY_SETTING}
defaultSetting: ${DW_DEFAULT_SETTING:-default value}
In general SubstitutingSourceProvider isn’t restricted to substitute environment variables but can be used to replace variables in the configuration source with arbitrary values by passing a custom StrSubstitutor implementation.
SSL support is built into Dropwizard. You will need to provide your own java keystore, which is outside the scope of this document (keytool is the command you need). There is a test keystore you can use in the Dropwizard example project.
server:
applicationConnectors:
- type: https
port: 8443
keyStorePath: example.keystore
keyStorePassword: example
validateCerts: false
Before a Dropwizard application can provide the command-line interface, parse a configuration file, or run as a server, it must first go through a bootstrapping phase. This phase corresponds to your Application subclass’s initialize method. You can add Bundles, Commands, or register Jackson modules to allow you to include custom types as part of your configuration class.
A Dropwizard Environment consists of all the Resources, servlets, filters, Health Checks, Jersey providers, Managed Objects, Tasks, and Jersey properties which your application provides.
Each Application subclass implements a run method. This is where you should be creating new resource instances, etc., and adding them to the given Environment class:
@Override
public void run(ExampleConfiguration config,
Environment environment) {
// encapsulate complicated setup logic in factories
final Thingy thingy = config.getThingyFactory().build();
environment.jersey().register(new ThingyResource(thingy));
environment.healthChecks().register("thingy", new ThingyHealthCheck(thingy));
}
It’s important to keep the run method clean, so if creating an instance of something is complicated, like the Thingy class above, extract that logic into a factory.
A health check is a runtime test which you can use to verify your application’s behavior in its production environment. For example, you may want to ensure that your database client is connected to the database:
public class DatabaseHealthCheck extends HealthCheck {
private final Database database;
public DatabaseHealthCheck(Database database) {
this.database = database;
}
@Override
protected Result check() throws Exception {
if (database.isConnected()) {
return Result.healthy();
} else {
return Result.unhealthy("Cannot connect to " + database.getUrl());
}
}
}
You can then add this health check to your application’s environment:
environment.healthChecks().register("database", new DatabaseHealthCheck(database));
By sending a GET request to /healthcheck on the admin port you can run these tests and view the results:
$ curl http://dw.example.com:8081/healthcheck
{"deadlocks":{"healthy":true},"database":{"healthy":true}}
If all health checks report success, a 200 OK is returned. If any fail, a 500 Internal Server Error is returned with the error messages and exception stack traces (if an exception was thrown).
All Dropwizard applications ship with the deadlocks health check installed by default, which uses Java 1.6’s built-in thread deadlock detection to determine if any threads are deadlocked.
Most applications involve objects which need to be started and stopped: thread pools, database connections, etc. Dropwizard provides the Managed interface for this. You can either have the class in question implement the #start() and #stop() methods, or write a wrapper class which does so. Adding a Managed instance to your application’s Environment ties that object’s lifecycle to that of the application’s HTTP server. Before the server starts, the #start() method is called. After the server has stopped (and after its graceful shutdown period) the #stop() method is called.
For example, given a theoretical Riak client which needs to be started and stopped:
public class RiakClientManager implements Managed {
private final RiakClient client;
public RiakClientManager(RiakClient client) {
this.client = client;
}
@Override
public void start() throws Exception {
client.start();
}
@Override
public void stop() throws Exception {
client.stop();
}
}
public class MyApplication extends Application<MyConfiguration>{
@Override
public void run(MyApplicationConfiguration configuration, Environment environment) {
RiakClient client = ...;
RiakClientManager riakClientManager = new RiakClientManager(client);
environment.lifecycle().manage(riakClientManager);
}
}
If RiakClientManager#start() throws an exception–e.g., an error connecting to the server–your application will not start and a full exception will be logged. If RiakClientManager#stop() throws an exception, the exception will be logged but your application will still be able to shut down.
It should be noted that Environment has built-in factory methods for ExecutorService and ScheduledExecutorService instances which are managed. See LifecycleEnvironment#executorService and LifecycleEnvironment#scheduledExecutorService for details.
A Dropwizard bundle is a reusable group of functionality, used to define blocks of an application’s behavior. For example, AssetBundle from the dropwizard-assets module provides a simple way to serve static assets from your application’s src/main/resources/assets directory as files available from /assets/* (or any other path) in your application.
Some bundles require configuration parameters. These bundles implement ConfiguredBundle and will require your application’s Configuration subclass to implement a specific interface.
Either your application or your static assets can be served from the root path, but not both. The latter is useful when using Dropwizard to back a Javascript application. To enable it, move your application to a sub-URL.
server:
rootPath: /api/
Note
If you use the Simple server configuration, then rootPath is calculated relatively from applicationContextPath. So, your API will be accessible from the path /application/api/
Then use an extended AssetsBundle constructor to serve resources in the assets folder from the root path. index.htm is served as the default page.
@Override
public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
bootstrap.addBundle(new AssetsBundle("/assets/", "/"));
}
When an AssetBundle is added to the application, it is registered as a servlet using a default name of assets. If the application needs to have multiple AssetBundle instances, the extended constructor should be used to specify a unique name for the AssetBundle.
@Override
public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
bootstrap.addBundle(new AssetsBundle("/assets/css", "/css", null, "css"));
bootstrap.addBundle(new AssetsBundle("/assets/js", "/js", null, "js"));
bootstrap.addBundle(new AssetsBundle("/assets/fonts", "/fonts", null, "fonts"));
}
Commands are basic actions which Dropwizard runs based on the arguments provided on the command line. The built-in server command, for example, spins up an HTTP server and runs your application. Each Command subclass has a name and a set of command line options which Dropwizard will use to parse the given command line arguments.
Below is an example on how to add a command and have Dropwizard recognize it.
public class MyCommand extends Command {
public MyCommand() {
// The name of our command is "hello" and the description printed is
// "Prints a greeting"
super("hello", "Prints a greeting");
}
@Override
public void configure(Subparser subparser) {
// Add a command line option
subparser.addArgument("-u", "--user")
.dest("user")
.type(String.class)
.required(true)
.help("The user of the program");
}
@Override
public void run(Bootstrap<?> bootstrap, Namespace namespace) throws Exception {
System.out.println("Hello " + namespace.getString("user"));
}
}
Dropwizard recognizes our command once we add it in the initialize stage of our application.
public class MyApplication extends Application<MyConfiguration>{
@Override
public void initialize(Bootstrap<DropwizardConfiguration> bootstrap) {
bootstrap.addCommand(new MyCommand());
}
}
To invoke the new functionality, run the following:
java -jar <jarfile> hello dropwizard
Some commands require access to configuration parameters and should extend the ConfiguredCommand class, using your application’s Configuration class as its type parameter. By default, Dropwizard will treat the last argument on the command line as the path to a YAML configuration file, parse and validate it, and provide your command with an instance of the configuration class.
A ConfiguredCommand can have additional command line options specified, while keeping the last argument the path to the YAML configuration.
@Override
public void configure(Subparser subparser) {
super.configure(subparser);
// Add a command line option
subparser.addArgument("-u", "--user")
.dest("user")
.type(String.class)
.required(true)
.help("The user of the program");
}
For more advanced customization of the command line (for example, having the configuration file location specified by -c), adapt the ConfiguredCommand class as needed.
A Task is a run-time action your application provides access to on the administrative port via HTTP. All Dropwizard applications start with: the gc task, which explicitly triggers the JVM’s garbage collection (This is useful, for example, for running full garbage collections during off-peak times or while the given application is out of rotation.); and the log-level task, which configures the level of any number of loggers at runtime (akin to Logback’s JmxConfigurator). The execute method of a Task can be annotated with @Timed, @Metered, and @ExceptionMetered. Dropwizard will automatically record runtime information about your tasks. Here’s a basic task class:
public class TruncateDatabaseTask extends Task {
private final Database database;
public TruncateDatabaseTask(Database database) {
super("truncate");
this.database = database;
}
@Override
public void execute(ImmutableMultimap<String, String> parameters, PrintWriter output) throws Exception {
this.database.truncate();
}
}
You can then add this task to your application’s environment:
environment.admin().addTask(new TruncateDatabaseTask(database));
Running a task can be done by sending a POST request to /tasks/{task-name} on the admin port. For example:
$ curl -X POST http://dw.example.com:8081/tasks/gc
Running GC...
Done!
Dropwizard uses Logback for its logging backend. It provides an slf4j implementation, and even routes all java.util.logging, Log4j, and Apache Commons Logging usage through Logback.
slf4j provides the following logging levels:
Dropwizard’s log format has a few specific goals:
The logging output looks like this:
TRACE [2010-04-06 06:42:35,271] com.example.dw.Thing: Contemplating doing a thing.
DEBUG [2010-04-06 06:42:35,274] com.example.dw.Thing: About to do a thing.
INFO [2010-04-06 06:42:35,274] com.example.dw.Thing: Doing a thing
WARN [2010-04-06 06:42:35,275] com.example.dw.Thing: Doing a thing
ERROR [2010-04-06 06:42:35,275] com.example.dw.Thing: This may get ugly.
! java.lang.RuntimeException: oh noes!
! at com.example.dw.Thing.run(Thing.java:16)
!
A few items of note:
All timestamps are in UTC and ISO 8601 format.
You can grep for messages of a specific level really easily:
tail -f dw.log | grep '^WARN'
You can grep for messages from a specific class or package really easily:
tail -f dw.log | grep 'com.example.dw.Thing'
You can even pull out full exception stack traces, plus the accompanying log message:
tail -f dw.log | grep -B 1 '^\!'
The ! prefix does not apply to syslog appenders, as stack traces are sent separately from the main message. Instead, t is used (this is the default value of the SyslogAppender that comes with Logback). This can be configured with the stackTracePrefix option when defining your appender.
You can specify a default logger level, override the levels of other loggers in your YAML configuration file, and even specify appenders for them. The latter form of configuration is preferable, but the former is also acceptable.
# Logging settings.
logging:
# The default level of all loggers. Can be OFF, ERROR, WARN, INFO, DEBUG, TRACE, or ALL.
level: INFO
# Logger-specific levels.
loggers:
# Overrides the level of com.example.dw.Thing and sets it to DEBUG.
"com.example.dw.Thing": DEBUG
# Enables the SQL query log and redirect it to a separate file
"org.hibernate.SQL":
level: DEBUG
# This line stops org.hibernate.SQL (or anything under it) from using the root logger
additive: false
appenders:
- type: file
currentLogFilename: ./logs/example-sql.log
archivedLogFilenamePattern: ./logs/example-sql-%d.log.gz
archivedFileCount: 5
By default, Dropwizard applications log INFO and higher to STDOUT. You can configure this by editing the logging section of your YAML configuration file:
logging:
appenders:
- type: console
threshold: WARN
target: stderr
In the above, we’re instead logging only WARN and ERROR messages to the STDERR device.
Dropwizard can also log to an automatically rotated set of log files. This is the recommended configuration for your production environment:
logging:
appenders:
- type: file
# The file to which current statements will be logged.
currentLogFilename: ./logs/example.log
# When the log file rotates, the archived log will be renamed to this and gzipped. The
# %d is replaced with the previous day (yyyy-MM-dd). Custom rolling windows can be created
# by passing a SimpleDateFormat-compatible format as an argument: "%d{yyyy-MM-dd-hh}".
archivedLogFilenamePattern: ./logs/example-%d.log.gz
# The number of archived files to keep.
archivedFileCount: 5
# The timezone used to format dates. HINT: USE THE DEFAULT, UTC.
timeZone: UTC
Finally, Dropwizard can also log statements to syslog.
Note
Because Java doesn’t use the native syslog bindings, your syslog server must have an open network socket.
logging:
appenders:
- type: syslog
# The hostname of the syslog server to which statements will be sent.
# N.B.: If this is the local host, the local syslog instance will need to be configured to
# listen on an inet socket, not just a Unix socket.
host: localhost
# The syslog facility to which statements will be sent.
facility: local0
You can combine any number of different appenders, including multiple instances of the same appender with different configurations:
logging:
# Permit DEBUG, INFO, WARN and ERROR messages to be logged by appenders.
level: DEBUG
appenders:
# Log warnings and errors to stderr
- type: console
threshold: WARN
target: stderr
# Log info, warnings and errors to our apps' main log.
# Rolled over daily and retained for 5 days.
- type: file
threshold: INFO
currentLogFilename: ./logs/example.log
archivedLogFilenamePattern: ./logs/example-%d.log.gz
archivedFileCount: 5
# Log debug messages, info, warnings and errors to our apps' debug log.
# Rolled over hourly and retained for 6 hours
- type: file
threshold: DEBUG
currentLogFilename: ./logs/debug.log
archivedLogFilenamePattern: ./logs/debug-%d{yyyy-MM-dd-hh}.log.gz
archivedFileCount: 6
All of Dropwizard’s APIs are designed with testability in mind, so even your applications can have unit tests:
public class MyApplicationTest {
private final Environment environment = mock(Environment.class);
private final JerseyEnvironment jersey = mock(JerseyEnvironment.class);
private final MyApplication application = new MyApplication();
private final MyConfiguration config = new MyConfiguration();
@Before
public void setup() throws Exception {
config.setMyParam("yay");
when(environment.jersey()).thenReturn(jersey);
}
@Test
public void buildsAThingResource() throws Exception {
application.run(config, environment);
verify(jersey).register(isA(ThingResource.class));
}
}
We highly recommend Mockito for all your mocking needs.
We think applications should print out a big ASCII art banner on startup. Yours should, too. It’s fun. Just add a banner.txt class to src/main/resources and it’ll print it out when your application starts:
INFO [2011-12-09 21:56:37,209] io.dropwizard.cli.ServerCommand: Starting hello-world
dP
88
.d8888b. dP. .dP .d8888b. 88d8b.d8b. 88d888b. 88 .d8888b.
88ooood8 `8bd8' 88' `88 88'`88'`88 88' `88 88 88ooood8
88. ... .d88b. 88. .88 88 88 88 88. .88 88 88. ...
`88888P' dP' `dP `88888P8 dP dP dP 88Y888P' dP `88888P'
88
dP
INFO [2011-12-09 21:56:37,214] org.eclipse.jetty.server.Server: jetty-7.6.0
...
We could probably make up an argument about why this is a serious devops best practice with high ROI and an Agile Tool, but honestly we just enjoy this.
We recommend you use TAAG for all your ASCII art banner needs.
Unsurprisingly, most of your day-to-day work with a Dropwizard application will be in the resource classes, which model the resources exposed in your RESTful API. Dropwizard uses Jersey for this, so most of this section is just re-hashing or collecting various bits of Jersey documentation.
Jersey is a framework for mapping various aspects of incoming HTTP requests to POJOs and then mapping various aspects of POJOs to outgoing HTTP responses. Here’s a basic resource class:
@Path("/{user}/notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class NotificationsResource {
private final NotificationStore store;
public NotificationsResource(NotificationStore store) {
this.store = store;
}
@GET
public NotificationList fetch(@PathParam("user") LongParam userId,
@QueryParam("count") @DefaultValue("20") IntParam count) {
final List<Notification> notifications = store.fetch(userId.get(), count.get());
if (notifications != null) {
return new NotificationList(userId, notifications);
}
throw new WebApplicationException(Status.NOT_FOUND);
}
@POST
public Response add(@PathParam("user") LongParam userId,
@Valid Notification notification) {
final long id = store.add(userId.get(), notification);
return Response.created(UriBuilder.fromResource(NotificationResource.class)
.build(userId.get(), id))
.build();
}
}
This class provides a resource (a user’s list of notifications) which responds to GET and POST requests to /{user}/notifications, providing and consuming application/json representations. There’s quite a lot of functionality on display here, and this section will explain in detail what’s in play and how to use these features in your application.
Important
Every resource class must have a @Path annotation.
The @Path annotation isn’t just a static string, it’s a URI Template. The {user} part denotes a named variable, and when the template matches a URI the value of that variable will be accessible via @PathParam-annotated method parameters.
For example, an incoming request for /1001/notifications would match the URI template, and the value "1001" would be available as the path parameter named user.
If your application doesn’t have a resource class whose @Path URI template matches the URI of an incoming request, Jersey will automatically return a 404 Not Found to the client.
Methods on a resource class which accept incoming requests are annotated with the HTTP methods they handle: @GET, @POST, @PUT, @DELETE, @HEAD, @OPTIONS, @PATCH.
Support for arbitrary new methods can be added via the @HttpMethod annotation. They also must be added to the list of allowed methods. This means, by default, methods such as CONNECT and TRACE are blocked, and will return a 405 Method Not Allowed response.
If a request comes in which matches a resource class’s path but has a method which the class doesn’t support, Jersey will automatically return a 405 Method Not Allowed to the client.
The return value of the method (in this case, a NotificationList instance) is then mapped to the negotiated media type this case, our resource only supports JSON, and so the NotificationList is serialized to JSON using Jackson.
Every resource method can be annotated with @Timed, @Metered, and @ExceptionMetered. Dropwizard augments Jersey to automatically record runtime information about your resource methods.
The annotated methods on a resource class can accept parameters which are mapped to from aspects of the incoming request. The *Param annotations determine which part of the request the data is mapped, and the parameter type determines how the data is mapped.
For example:
What’s noteworthy here is that you can actually encapsulate the vast majority of your validation logic using specialized parameter objects. See AbstractParam for details.
If you’re handling request entities (e.g., an application/json object on a PUT request), you can model this as a parameter without a *Param annotation. In the example code, the add method provides a good example of this:
@POST
public Response add(@PathParam("user") LongParam userId,
@Valid Notification notification) {
final long id = store.add(userId.get(), notification);
return Response.created(UriBuilder.fromResource(NotificationResource.class)
.build(userId.get(), id)
.build();
}
Jersey maps the request entity to any single, unbound parameter. In this case, because the resource is annotated with @Consumes(MediaType.APPLICATION_JSON), it uses the Dropwizard-provided Jackson support which, in addition to parsing the JSON and mapping it to an instance of Notification, also runs that instance through Dropwizard’s Constraining Entities.
If the deserialized Notification isn’t valid, Dropwizard returns a 422 Unprocessable Entity response to the client.
Note
If your request entity parameter isn’t annotated with @Valid, it won’t be validated.
Jersey also provides full content negotiation, so if your resource class consumes application/json but the client sends a text/plain entity, Jersey will automatically reply with a 406 Not Acceptable. Jersey’s even smart enough to use client-provided q-values in their Accept headers to pick the best response content type based on what both the client and server will support.
If your clients are expecting custom headers or additional information (or, if you simply desire an additional degree of control over your responses), you can return explicitly-built Response objects:
return Response.noContent().language(Locale.GERMAN).build();
In general, though, we recommend you return actual domain objects if at all possible. It makes testing resources much easier.
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.
While Jersey doesn’t quite have first-class support for hyperlink-driven applications, the provided UriBuilder functionality does quite well.
Rather than duplicate resource URIs, it’s possible (and recommended!) to initialize a UriBuilder with the path from the resource class itself:
UriBuilder.fromResource(UserResource.class).build(user.getId());
As with just about everything in Dropwizard, we recommend you design your resources to be testable. Dependencies which aren’t request-injected should be passed in via the constructor and assigned to final fields.
Testing, then, consists of creating an instance of your resource class and passing it a mock. (Again: Mockito.)
public class NotificationsResourceTest {
private final NotificationStore store = mock(NotificationStore.class);
private final NotificationsResource resource = new NotificationsResource(store);
@Test
public void getsReturnNotifications() {
final List<Notification> notifications = mock(List.class);
when(store.fetch(1, 20)).thenReturn(notifications);
final NotificationList list = resource.fetch(new LongParam("1"), new IntParam("20"));
assertThat(list.getUserId(),
is(1L));
assertThat(list.getNotifications(),
is(notifications));
}
}
Adding a Cache-Control statement to your resource class is simple with Dropwizard:
@GET
@CacheControl(maxAge = 6, maxAgeUnit = TimeUnit.HOURS)
public String getCachableValue() {
return "yay";
}
The @CacheControl annotation will take all of the parameters of the Cache-Control header.
Representation classes are classes which, when handled to various Jersey MessageBodyReader and MessageBodyWriter providers, become the entities in your application’s API. Dropwizard heavily favors JSON, but it’s possible to map from any POJO to custom formats and back.
Jackson is awesome at converting regular POJOs to JSON and back. This file:
public class Notification {
private String text;
public Notification(String text) {
this.text = text;
}
@JsonProperty
public String getText() {
return text;
}
@JsonProperty
public void setText(String text) {
this.text = text;
}
}
gets converted into this JSON:
{
"text": "hey it's the value of the text field"
}
If, at some point, you need to change the JSON field name or the Java field without affecting the other, you can add an explicit field name to the @JsonProperty annotation.
If you prefer immutable objects rather than JavaBeans, that’s also doable:
public class Notification {
private final String text;
@JsonCreator
public Notification(@JsonProperty("text") String text) {
this.text = text;
}
@JsonProperty("text")
public String getText() {
return text;
}
}
Not all JSON representations map nicely to the objects your application deals with, so it’s sometimes necessary to use custom serializers and deserializers. Just annotate your object like this:
@JsonSerialize(using=FunkySerializer.class)
@JsonDeserialize(using=FunkyDeserializer.class)
public class Funky {
// ...
}
Then make a FunkySerializer class which implements JsonSerializer<Funky> and a FunkyDeserializer class which implements JsonDeserializer<Funky>.
A common issue with JSON is the disagreement between camelCase and snake_case field names. Java and Javascript folks tend to like camelCase; Ruby, Python, and Perl folks insist on snake_case. To make Dropwizard automatically convert field names to snake_case (and back), just annotate the class with @JsonSnakeCase:
@JsonSnakeCase
public class Person {
private final String firstName;
@JsonCreator
public Person(@JsonProperty String firstName) {
this.firstName = firstName;
}
@JsonProperty
public String getFirstName() {
return firstName;
}
}
This gets converted into this JSON:
{
"first_name": "Coda"
}
If your application happens to return lots of information, you may get a big performance and efficiency bump by using streaming output. By returning an object which implements Jersey’s StreamingOutput interface, your method can stream the response entity in a chunk-encoded output stream. Otherwise, you’ll need to fully construct your return value and then hand it off to be sent to the client.
For generating HTML pages, check out Dropwizard’s views support.
Sometimes, though, you’ve got some wacky output format you need to produce or consume and no amount of arguing will make JSON acceptable. That’s unfortunate but OK. You can add support for arbitrary input and output formats by creating classes which implement Jersey’s MessageBodyReader<T> and MessageBodyWriter<T> interfaces. (Make sure they’re annotated with @Provider and @Produces("text/gibberish") or @Consumes("text/gibberish").) Once you’re done, just add instances of them (or their classes if they depend on Jersey’s @Context injection) to your application’s Environment on initialization.
There might be cases when you want to filter out requests or modify them before they reach your Resources. Jersey has a rich api for filters and interceptors that can be used directly in Dropwizard. You can stop the request from reaching your resources by throwing a WebApplicationException. Alternatively, you can use filters to modify inbound requests or outbound responses.
@Provider
public class DateNotSpecifiedFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String dateHeader = requestContext.getHeaderString(HttpHeaders.DATE);
if (dateHeader == null) {
Exception cause = new IllegalArgumentException("Date Header was not specified");
throw new WebApplicationException(cause, Response.Status.BAD_REQUEST);
}
}
}
This example filter checks the request for the “Date” header, and denies the request if was missing. Otherwise, the request is passed through.
Filters can be dynamically bound to resource methods using DynamicFeature:
@Provider
public class DateRequiredFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (resourceInfo.getResourceMethod().getAnnotation(DateRequired.class) != null) {
context.register(DateNotSpecifiedFilter.class);
}
}
}
The DynamicFeature is invoked by the Jersey runtime when the application is started. In this example, the feature checks for methods that are annotated with @DateRequired and registers the DateNotSpecified filter on those methods only.
You typically register the feature in your Application class, like so:
environment.jersey().register(DateRequiredFeature.class);
Another way to create filters is by creating servlet filters. They offer a way to to register filters that apply both to servlet requests as well as resource requests. Jetty comes with a few bundled filters which may already suit your needs. If you want to create your own filter, this example demonstrates a servlet filter analogous to the previous example:
public class DateNotSpecifiedServletFilter implements javax.servlet.Filter {
// Other methods in interface omitted for brevity
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
String dateHeader = ((HttpServletRequest) request).getHeader(HttpHeaders.DATE);
if (dateHeader != null) {
chain.doFilter(request, response); // This signals that the request should pass this filter
} else {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpStatus.BAD_REQUEST_400);
httpResponse.getWriter().print("Date Header was not specified");
}
}
}
}
This servlet filter can then be registered in your Application class by wrapping it in FilterHolder and adding it to the application context together with a specification for which paths this filter should active. Here’s an example:
environment.servlets().addFilter("DateNotSpecifiedServletFilter", new DateNotSpecifiedServletFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
When your application starts up, it will spin up a Jetty HTTP server, see DefaultServerFactory. This server will have two handlers, one for your application port and the other for your admin port. The admin handler creates and registers the AdminServlet. This has a handle to all of the application healthchecks and metrics via the ServletContext.
The application port has an HttpServlet as well, this is composed of DropwizardResourceConfig, which is an extension of Jersey’s resource configuration that performs scanning to find root resource and provider classes. Ultimately when you call env.jersey().register(new SomeResource()), you are adding to the DropwizardResourceConfig. This config is a jersey Application, so all of your application resources are served from one Servlet
DropwizardResourceConfig is where the various ResourceMethodDispatchAdapter are registered to enable the following functionality:
- Resource method requests with @Timed, @Metered, @ExceptionMetered are delegated to special dispatchers which decorate the metric telemetry
- Resources that return Guava Optional are unboxed. Present returns underlying type, and non-present 404s
- Resource methods that are annotated with @CacheControl are delegated to a special dispatcher that decorates on the cache control headers
- Enables using Jackson to parse request entities into objects and generate response entities from objects, all while performing validation