Dropwizard pulls together stable, mature libraries from the Java ecosystem into a simple, light-weight package that lets you focus on getting things done.
Dropwizard has out-of-the-box support for sophisticated configuration, application metrics, logging, operational tools, and much more, allowing you and your team to ship a production-quality web service in the shortest time possible.
Getting Started will guide you through the process of creating a simple Dropwizard Project: Hello World. Along the way, we’ll explain the various underlying libraries and their roles, important concepts in Dropwizard, and suggest some organizational techniques to help you as your project grows. (Or you can just skip to the fun part.)
Dropwizard straddles the line between being a library and a framework. Its goal is to provide performant, reliable implementations of everything a production-ready web application needs. Because this functionality is extracted into a reusable library, your application remains lean and focused, reducing both time-to-market and maintenance burdens.
Because you can’t be a web application without HTTP, Dropwizard uses the Jetty HTTP library to
embed an incredibly tuned HTTP server directly into your project. Instead of handing your
application off to a complicated application server, Dropwizard projects have a main
method
which spins up an HTTP server. Running your application as a simple process eliminates a number of
unsavory aspects of Java in production (no PermGen issues, no application server configuration and
maintenance, no arcane deployment tools, no class loader troubles, no hidden application logs, no
trying to tune a single garbage collector to work with multiple application workloads) and allows
you to use all of the existing Unix process management tools instead.
For building RESTful web applications, we’ve found nothing beats Jersey (the JAX-RS reference
implementation) in terms of features or performance. It allows you to write clean, testable classes
which gracefully map HTTP requests to simple Java objects. It supports streaming output, matrix URI
parameters, conditional GET
requests, and much, much more.
In terms of data formats, JSON has become the web’s lingua franca, and Jackson is the king of JSON on the JVM. In addition to being lightning fast, it has a sophisticated object mapper, allowing you to export your domain models directly.
The Metrics library rounds things out, providing you with unparalleled insight into your code’s behavior in your production environment.
In addition to Jetty, Jersey, and Jackson, Dropwizard also includes a number of libraries to help you ship more quickly and with fewer regrets.
Now that you’ve gotten the lay of the land, let’s dig in!
We recommend you use Maven for new Dropwizard applications. If you’re a big Ant / Ivy, Buildr, Gradle, SBT, Leiningen, or Gant fan, that’s cool, but we use Maven, and we’ll be using Maven as we go through this example application. If you have any questions about how Maven works, Maven: The Complete Reference should have what you’re looking for. (We’re assuming you know how to create a new Maven project. If not, you can use this to get started.)
First, add a dropwizard.version
property to your POM with the current version of Dropwizard
(which is @project.version@):
<properties>
<dropwizard.version>INSERT VERSION HERE</dropwizard.version>
</properties>
Add the dropwizard-core
library as a dependency:
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>${dropwizard.version}</version>
</dependency>
</dependencies>
Alright, that’s enough XML. We’ve got a Maven project set up now, and it’s time to start writing real code.
Each Dropwizard application has its own subclass of the Configuration
class which specifies
environment-specific parameters. These parameters are specified in a YAML configuration file which
is deserialized to an instance of your application’s configuration class and validated.
The application we’ll be building is a high-performance Hello World service, and one of our requirements is that we need to be able to vary how it says hello from environment to environment. We’ll need to specify at least two things to begin with: a template for saying hello and a default name to use in case the user doesn’t specify their name.
Here’s what our configuration class will look like, full example conf here:
package com.example.helloworld;
import io.dropwizard.Configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
public class HelloWorldConfiguration extends Configuration {
@NotEmpty
private String template;
@NotEmpty
private String defaultName = "Stranger";
@JsonProperty
public String getTemplate() {
return template;
}
@JsonProperty
public void setTemplate(String template) {
this.template = template;
}
@JsonProperty
public String getDefaultName() {
return defaultName;
}
@JsonProperty
public void setDefaultName(String name) {
this.defaultName = name;
}
}
There’s a lot going on here, so let’s unpack a bit of it.
When this class is deserialized from the YAML file, it will pull two root-level fields from the YAML
object: template
, the template for our Hello World saying, and defaultName
, the default name
to use. Both template
and defaultName
are annotated with @NotEmpty
, so if the YAML
configuration file has blank values for either or is missing template
entirely an informative
exception will be thrown, and your application won’t start.
Both the getters and setters for template
and defaultName
are annotated with
@JsonProperty
, which allows Jackson to both deserialize the properties from a YAML file but also
to serialize it.
Note
The mapping from YAML to your application’s Configuration
instance is done
by Jackson. This means your Configuration
class can use all of
Jackson’s object-mapping annotations. The validation of @NotEmpty
is
handled by Hibernate Validator, which has a
wide range of built-in constraints for you to use.
Our YAML file will then look like the below, full example yml here:
template: Hello, %s!
defaultName: Stranger
Dropwizard has many more configuration parameters than that, but they all have sane defaults so you can keep your configuration files small and focused.
So save that YAML file as hello-world.yml
, because we’ll be getting up and running pretty soon,
and we’ll need it. Next up, we’re creating our application class!
Combined with your project’s Configuration
subclass, its Application
subclass forms the core
of your Dropwizard application. The Application
class pulls together the various bundles and
commands which provide basic functionality. (More on that later.) For now, though, our
HelloWorldApplication
looks like this:
package com.example.helloworld;
import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import com.example.helloworld.resources.HelloWorldResource;
import com.example.helloworld.health.TemplateHealthCheck;
public class HelloWorldApplication extends Application<HelloWorldConfiguration> {
public static void main(String[] args) throws Exception {
new HelloWorldApplication().run(args);
}
@Override
public String getName() {
return "hello-world";
}
@Override
public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
// nothing to do yet
}
@Override
public void run(HelloWorldConfiguration configuration,
Environment environment) {
// nothing to do yet
}
}
As you can see, HelloWorldApplication
is parameterized with the application’s configuration
type, HelloWorldConfiguration
. An initialize
method is used to configure aspects of the
application required before the application is run, like bundles, configuration source providers,
etc. Also, we’ve added a static
main
method, which will be our application’s entry point.
Right now, we don’t have any functionality implemented, so our run
method is a little boring.
Let’s fix that!
Before we can get into the nuts-and-bolts of our Hello World application, we need to stop and think about our API. Luckily, our application needs to conform to an industry standard, RFC 1149, which specifies the following JSON representation of a Hello World saying:
{
"id": 1,
"content": "Hi!"
}
The id
field is a unique identifier for the saying, and content
is the textual
representation of the saying. (Thankfully, this is a fairly straight-forward industry standard.)
To model this representation, we’ll create a representation class:
package com.example.helloworld.api;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.Length;
public class Saying {
private long id;
@Length(max = 3)
private String content;
public Saying() {
// Jackson deserialization
}
public Saying(long id, String content) {
this.id = id;
this.content = content;
}
@JsonProperty
public long getId() {
return id;
}
@JsonProperty
public String getContent() {
return content;
}
}
This is a pretty simple POJO, but there are a few things worth noting here.
First, it’s immutable. This makes Saying
instances very easy to reason about in multi-threaded
environments as well as single-threaded environments. Second, it uses the JavaBeans standard for the
id
and content
properties. This allows Jackson to serialize it to the JSON we need. The
Jackson object mapping code will populate the id
field of the JSON object with the return value
of #getId()
, likewise with content
and #getContent()
. Lastly, the bean leverages validation to ensure the content size is no greater than 3.
Note
The JSON serialization here is done by Jackson, which supports far more than simple JavaBean objects like this one. In addition to the sophisticated set of annotations, you can even write your custom serializers and deserializers.
Now that we’ve got our representation class, it makes sense to start in on the resource it represents.
Jersey resources are the meat-and-potatoes of a Dropwizard application. Each resource class is
associated with a URI template. For our application, we need a resource which returns new Saying
instances from the URI /hello-world
, so our resource class looks like this:
package com.example.helloworld.resources;
import com.example.helloworld.api.Saying;
import com.google.common.base.Optional;
import com.codahale.metrics.annotation.Timed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.atomic.AtomicLong;
@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public class HelloWorldResource {
private final String template;
private final String defaultName;
private final AtomicLong counter;
public HelloWorldResource(String template, String defaultName) {
this.template = template;
this.defaultName = defaultName;
this.counter = new AtomicLong();
}
@GET
@Timed
public Saying sayHello(@QueryParam("name") Optional<String> name) {
final String value = String.format(template, name.or(defaultName));
return new Saying(counter.incrementAndGet(), value);
}
}
Finally, we’re in the thick of it! Let’s start from the top and work our way down.
HelloWorldResource
has two annotations: @Path
and @Produces
. @Path("/hello-world")
tells Jersey that this resource is accessible at the URI /hello-world
, and
@Produces(MediaType.APPLICATION_JSON)
lets Jersey’s content negotiation code know that this
resource produces representations which are application/json
.
HelloWorldResource
takes two parameters for construction: the template
it uses to produce
the saying and the defaultName
used when the user declines to tell us their name. An
AtomicLong
provides us with a cheap, thread-safe way of generating unique(ish) IDs.
Warning
Resource classes are used by multiple threads concurrently. In general, we recommend that resources be stateless/immutable, but it’s important to keep the context in mind.
#sayHello(Optional<String>)
is the meat of this class, and it’s a fairly simple method. The
@QueryParam("name")
annotation tells Jersey to map the name
parameter from the query string
to the name
parameter in the method. If the client sends a request to
/hello-world?name=Dougie
, sayHello
will be called with Optional.of("Dougie")
; if there
is no name
parameter in the query string, sayHello
will be called with
Optional.absent()
. (Support for Guava’s Optional
is a little extra sauce that Dropwizard
adds to Jersey’s existing functionality.)
Note
If the client sends a request to /hello-world?name=
, sayHello
will be called with
Optional.of("")
. This may seem odd at first, but this follows the standards (an application
may have different behavior depending on if a parameter is empty vs nonexistent). You can swap
Optional<String>
parameter with NonEmptyStringParam
if you want /hello-world?name=
to return “Hello, Stranger!” For more information on resource parameters see
the documentation
Inside the sayHello
method, we increment the counter, format the template using
String.format(String, Object...)
, and return a new Saying
instance.
Because sayHello
is annotated with @Timed
, Dropwizard automatically records the duration and
rate of its invocations as a Metrics Timer.
Once sayHello
has returned, Jersey takes the Saying
instance and looks for a provider class
which can write Saying
instances as application/json
. Dropwizard has one such provider built
in which allows for producing and consuming Java objects as JSON objects. The provider writes out
the JSON and the client receives a 200 OK
response with a content type of application/json
.
Before that will actually work, though, we need to go back to HelloWorldApplication
and add this
new resource class. In its run
method we can read the template and default name from the
HelloWorldConfiguration
instance, create a new HelloWorldResource
instance, and then add
it to the application’s Jersey environment:
@Override
public void run(HelloWorldConfiguration configuration,
Environment environment) {
final HelloWorldResource resource = new HelloWorldResource(
configuration.getTemplate(),
configuration.getDefaultName()
);
environment.jersey().register(resource);
}
When our application starts, we create a new instance of our resource class with the parameters from
the configuration file and hand it off to the Environment
, which acts like a registry of all the
things your application can do.
Note
A Dropwizard application can contain many resource classes, each corresponding to its own URI
pattern. Just add another @Path
-annotated resource class and call register
with an
instance of the new class.
Before we go too far, we should add a health check for our application.
Health checks give you a way of adding small tests to your application to allow you to verify that your application is functioning correctly in production. We strongly recommend that all of your applications have at least a minimal set of health checks.
Note
We recommend this so strongly, in fact, that Dropwizard will nag you should you neglect to add a health check to your project.
Since formatting strings is not likely to fail while an application is running (unlike, say, a database connection pool), we’ll have to get a little creative here. We’ll add a health check to make sure we can actually format the provided template:
package com.example.helloworld.health;
import com.codahale.metrics.health.HealthCheck;
public class TemplateHealthCheck extends HealthCheck {
private final String template;
public TemplateHealthCheck(String template) {
this.template = template;
}
@Override
protected Result check() throws Exception {
final String saying = String.format(template, "TEST");
if (!saying.contains("TEST")) {
return Result.unhealthy("template doesn't include a name");
}
return Result.healthy();
}
}
TemplateHealthCheck
checks for two things: that the provided template is actually a well-formed
format string, and that the template actually produces output with the given name.
If the string is not a well-formed format string (for example, someone accidentally put
Hello, %s%
in the configuration file), then String.format(String, Object...)
will throw an
IllegalFormatException
and the health check will implicitly fail. If the rendered saying doesn’t
include the test string, the health check will explicitly fail by returning an unhealthy Result
.
As with most things in Dropwizard, we create a new instance with the appropriate parameters and add
it to the Environment
:
@Override
public void run(HelloWorldConfiguration configuration,
Environment environment) {
final HelloWorldResource resource = new HelloWorldResource(
configuration.getTemplate(),
configuration.getDefaultName()
);
final TemplateHealthCheck healthCheck =
new TemplateHealthCheck(configuration.getTemplate());
environment.healthChecks().register("template", healthCheck);
environment.jersey().register(resource);
}
Now we’re almost ready to go!
We recommend that you build your Dropwizard applications as “fat” JAR files — single .jar
files
which contain all of the .class
files required to run your application. This allows you to
build a single deployable artifact which you can promote from your staging environment to your QA
environment to your production environment without worrying about differences in installed
libraries. To start building our Hello World application as a fat JAR, we need to configure a Maven
plugin called maven-shade
. In the <build><plugins>
section of your pom.xml
file, add
this:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.helloworld.HelloWorldApplication</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
This configures Maven to do a couple of things during its package
phase:
pom.xml
file which doesn’t include dependencies for the libraries whose contents are
included in the fat JAR.META-INF/services
entries in the JARs instead of overwriting them.
(Neither Dropwizard nor Jersey works without those.)com.example.helloworld.HelloWorldApplication
as the JAR’s MainClass
. This will allow
you to run the JAR using java -jar
.Warning
If your application has a dependency which must be signed (e.g., a JCA/JCE provider or
other trusted library), you have to add an exclusion to the maven-shade-plugin
configuration for that library and include that JAR in the classpath.
Warning
Since Dropwizard is using the Java ServiceLoader functionality to register and load extensions, the minimizeJar option of the maven-shade-plugin will lead to non-working application JARs.
Dropwizard can also use the project version if it’s embedded in the JAR’s manifest as the
Implementation-Version
. To embed this information using Maven, add the following to the
<build><plugins>
section of your pom.xml
file:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
This can be handy when trying to figure out what version of your application you have deployed on a machine.
Once you’ve got that configured, go into your project directory and run mvn package
(or run the
package
goal from your IDE). You should see something like this:
[INFO] Including org.eclipse.jetty:jetty-util:jar:7.6.0.RC0 in the shaded jar.
[INFO] Including com.google.guava:guava:jar:10.0.1 in the shaded jar.
[INFO] Including com.google.code.findbugs:jsr305:jar:1.3.9 in the shaded jar.
[INFO] Including org.hibernate:hibernate-validator:jar:4.2.0.Final in the shaded jar.
[INFO] Including javax.validation:validation-api:jar:1.0.0.GA in the shaded jar.
[INFO] Including org.yaml:snakeyaml:jar:1.9 in the shaded jar.
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing /Users/yourname/Projects/hello-world/target/hello-world-0.0.1-SNAPSHOT.jar with /Users/yourname/Projects/hello-world/target/hello-world-0.0.1-SNAPSHOT-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.415s
[INFO] Finished at: Fri Dec 02 16:26:42 PST 2011
[INFO] Final Memory: 11M/81M
[INFO] ------------------------------------------------------------------------
Congratulations! You’ve built your first Dropwizard project! Now it’s time to run it!
Now that you’ve built a JAR file, it’s time to run it.
In your project directory, run this:
java -jar target/hello-world-0.0.1-SNAPSHOT.jar
You should see something like the following:
usage: java -jar hello-world-0.0.1-SNAPSHOT.jar
[-h] [-v] {server} ...
positional arguments:
{server} available commands
optional arguments:
-h, --help show this help message and exit
-v, --version show the service version and exit
Dropwizard takes the first command line argument and dispatches it to a matching command. In this
case, the only command available is server
, which runs your application as an HTTP server. The
server
command requires a configuration file, so let’s go ahead and give it
the YAML file we previously saved:
java -jar target/hello-world-0.0.1-SNAPSHOT.jar server hello-world.yml
You should see something like the following:
INFO [2011-12-03 00:38:32,927] io.dropwizard.cli.ServerCommand: Starting hello-world
INFO [2011-12-03 00:38:32,931] org.eclipse.jetty.server.Server: jetty-7.x.y-SNAPSHOT
INFO [2011-12-03 00:38:32,936] org.eclipse.jetty.server.handler.ContextHandler: started o.e.j.s.ServletContextHandler{/,null}
INFO [2011-12-03 00:38:32,999] com.sun.jersey.server.impl.application.WebApplicationImpl: Initiating Jersey application, version 'Jersey: 1.10 11/02/2011 03:53 PM'
INFO [2011-12-03 00:38:33,041] io.dropwizard.setup.Environment:
GET /hello-world (com.example.helloworld.resources.HelloWorldResource)
INFO [2011-12-03 00:38:33,215] org.eclipse.jetty.server.handler.ContextHandler: started o.e.j.s.ServletContextHandler{/,null}
INFO [2011-12-03 00:38:33,235] org.eclipse.jetty.server.AbstractConnector: Started BlockingChannelConnector@0.0.0.0:8080 STARTING
INFO [2011-12-03 00:38:33,238] org.eclipse.jetty.server.AbstractConnector: Started SocketConnector@0.0.0.0:8081 STARTING
Your Dropwizard application is now listening on ports 8080
for application requests and 8081
for administration requests. If you press ^C
, the application will shut down gracefully, first
closing the server socket, then waiting for in-flight requests to be processed, then shutting down
the process itself.
However, while it’s up, let’s give it a whirl! Click here to say hello! Click here to get even friendlier!
So, we’re generating sayings. Awesome. But that’s not all your application can do. One of the main reasons for using Dropwizard is the out-of-the-box operational tools it provides, all of which can be found on the admin port.
If you click through to the metrics resource, you can see all of your application’s metrics represented as a JSON object.
The threads resource allows you to quickly get a thread dump of all the threads running in that process.
Hint
When a Jetty worker thread is handling an incoming HTTP request, the thread name is set to the method and URI of the request. This can be very helpful when debugging a poorly-behaving request.
The healthcheck resource runs the health check class we wrote. You should see something like this:
* deadlocks: OK
* template: OK
template
here is the result of your TemplateHealthCheck
, which unsurprisingly passed.
deadlocks
is a built-in health check which looks for deadlocked JVM threads and prints out a
listing if any are found.
Well, congratulations. You’ve got a Hello World application ready for production (except for the lack of tests) that’s capable of doing 30,000-50,000 requests per second. Hopefully, you’ve gotten a feel for how Dropwizard combines Jetty, Jersey, Jackson, and other stable, mature libraries to provide a phenomenal platform for developing RESTful web applications.
There’s a lot more to Dropwizard than is covered here (commands, bundles, servlets, advanced configuration, validation, HTTP clients, database clients, views, etc.), all of which is covered by the User Manual.
This goal of this document is to provide you with all the information required to build, organize, test, deploy, and maintain Dropwizard-based applications. If you’re new to Dropwizard, you should read the Getting Started guide first.
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:
com.example.myapplication
:api
: Representations.cli
: Commandsclient
: Client implementation for your applicationcore
: Domain implementationjdbi
: Database access classeshealth
: Health Checksresources
: ResourcesMyApplication
: The application classMyApplicationConfiguration
: configuration classThe 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:
ERROR
WARN
INFO
DEBUG
TRACE
DEBUG
level.Dropwizard’s log format has a few specific goals:
tail
and grep
.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.
@Timed
measures the duration of requests to a resource@Metered
measures the rate at which the resource is accessed@ExceptionMetered
measures how often exceptions occur processing the resourceThe 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:
@PathParam("user")
-annotated String
takes the raw value from the user
variable in
the matched URI template and passes it into the method as a String
.@QueryParam("count")
-annotated IntParam
parameter takes the first count
value from
the request’s query string and passes it as a String
to IntParam
’s constructor.
IntParam
(and all other io.dropwizard.jersey.params.*
classes) parses the string
as an Integer
, returning a 400 Bad Request
if the value is malformed.@FormParam("name")
-annotated Set<String>
parameter takes all the name
values from a
posted form and passes them to the method as a set of strings.*Param
–annotated NonEmptyStringParam
will interpret empty strings as absent strings,
which is useful in cases where the endpoint treats empty strings and absent strings as
interchangeable.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>
.
snake_case
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
The dropwizard-client
module provides you with two different performant,
instrumented HTTP clients so you can integrate your service with other web
services: Apache HttpClient and Jersey Client.
The underlying library for dropwizard-client
is Apache’s HttpClient, a full-featured,
well-tested HTTP client library.
To create a managed, instrumented HttpClient
instance, your
configuration class needs an http client configuration instance:
public class ExampleConfiguration extends Configuration {
@Valid
@NotNull
private HttpClientConfiguration httpClient = new HttpClientConfiguration();
@JsonProperty("httpClient")
public HttpClientConfiguration getHttpClientConfiguration() {
return httpClient;
}
@JsonProperty("httpClient")
public void setHttpClientConfiguration(HttpClientConfiguration httpClient) {
this.httpClient = httpClient;
}
}
Then, in your application’s run
method, create a new HttpClientBuilder
:
@Override
public void run(ExampleConfiguration config,
Environment environment) {
final HttpClient httpClient = new HttpClientBuilder(environment).using(config.getHttpClientConfiguration())
.build("example-http-client");
environment.jersey().register(new ExternalServiceResource(httpClient));
}
Dropwizard’s HttpClientBuilder
actually gives you an instrumented subclass which tracks the
following pieces of data:
org.apache.http.conn.ClientConnectionManager.available-connections
org.apache.http.conn.ClientConnectionManager.leased-connections
org.apache.http.conn.ClientConnectionManager.max-connections
org.apache.http.conn.ClientConnectionManager.pending-connections
org.apache.http.client.HttpClient.get-requests
GET
requests are being sent.org.apache.http.client.HttpClient.post-requests
POST
requests are being sent.org.apache.http.client.HttpClient.head-requests
HEAD
requests are being sent.org.apache.http.client.HttpClient.put-requests
PUT
requests are being sent.org.apache.http.client.HttpClient.delete-requests
DELETE
requests are being sent.org.apache.http.client.HttpClient.options-requests
OPTIONS
requests are being sent.org.apache.http.client.HttpClient.trace-requests
TRACE
requests are being sent.org.apache.http.client.HttpClient.connect-requests
CONNECT
requests are being sent.org.apache.http.client.HttpClient.move-requests
MOVE
requests are being sent.org.apache.http.client.HttpClient.patch-requests
PATCH
requests are being sent.org.apache.http.client.HttpClient.other-requests
Note
The naming strategy for the metrics associated requests is configurable.
Specifically, the last part e.g. get-requests.
What is displayed is HttpClientMetricNameStrategies.METHOD_ONLY
, you can
also include the host via HttpClientMetricNameStrategies.HOST_AND_METHOD
or a url without query string via HttpClientMetricNameStrategies.QUERYLESS_URL_AND_METHOD
If HttpClient is too low-level for you, Dropwizard also supports Jersey’s Client API.
Jersey’s Client
allows you to use all of the server-side media type support that your service
uses to, for example, deserialize application/json
request entities as POJOs.
To create a managed, instrumented JerseyClient
instance, your
configuration class needs an jersey client configuration instance:
public class ExampleConfiguration extends Configuration {
@Valid
@NotNull
private JerseyClientConfiguration jerseyClient = new JerseyClientConfiguration();
@JsonProperty("jerseyClient")
public JerseyClientConfiguration getJerseyClientConfiguration() {
return jerseyClient;
}
}
Then, in your service’s run
method, create a new JerseyClientBuilder
:
@Override
public void run(ExampleConfiguration config,
Environment environment) {
final Client client = new JerseyClientBuilder(environment).using(config.getJerseyClientConfiguration())
.build(getName());
environment.jersey().register(new ExternalServiceResource(client));
}
The Client that Dropwizard creates deviates from the Jersey Client Configuration defaults. The default, in Jersey, is for a client to never timeout reading or connecting in a request, while in Dropwizard, the default is 500 milliseconds.
There are a couple of ways to change this behavior. The recommended way is to modify the
YAML configuration. Alternatively, set the properties on
the JerseyClientConfiguration
, which will take effect for all built clients. On a per client
basis, the configuration can be changed by utilizing the property
method and, in this case,
the Jersey Client Properties can be used.
Warning
Do not try to change Jersey properties using Jersey Client Properties through the
withProperty(String propertyName, Object propertyValue)
method on the JerseyClientBuilder
, because by default it’s configured by Dropwizard’s
HttpClientBuilder
, so the Jersey properties are ignored.
The dropwizard-jdbi
module provides you with managed access to JDBI, a flexible and
modular library for interacting with relational databases via SQL.
To create a managed, instrumented DBI
instance, your
configuration class needs a DataSourceFactory
instance:
public class ExampleConfiguration extends Configuration {
@Valid
@NotNull
private DataSourceFactory database = new DataSourceFactory();
@JsonProperty("database")
public void setDataSourceFactory(DataSourceFactory factory) {
this.database = factory;
}
@JsonProperty("database")
public DataSourceFactory getDataSourceFactory() {
return database;
}
}
Then, in your service’s run
method, create a new DBIFactory
:
@Override
public void run(ExampleConfiguration config, Environment environment) {
final DBIFactory factory = new DBIFactory();
final DBI jdbi = factory.build(environment, config.getDataSourceFactory(), "postgresql");
final UserDAO dao = jdbi.onDemand(UserDAO.class);
environment.jersey().register(new UserResource(dao));
}
This will create a new managed connection pool to the database, a
health check for connectivity to the database, and a new DBI
instance for you to use.
Your service’s configuration file will then look like this:
database:
# the name of your JDBC driver
driverClass: org.postgresql.Driver
# the username
user: pg-user
# the password
password: iAMs00perSecrEET
# the JDBC URL
url: jdbc:postgresql://db.example.com/db-prod
# any properties specific to your JDBC driver:
properties:
charSet: UTF-8
# the maximum amount of time to wait on an empty pool before throwing an exception
maxWaitForConnection: 1s
# the SQL query to run when validating a connection's liveness
validationQuery: "/* MyService Health Check */ SELECT 1"
# the timeout before a connection validation queries fail
validationQueryTimeout: 3s
# the minimum number of connections to keep open
minSize: 8
# the maximum number of connections to keep open
maxSize: 32
# whether or not idle connections should be validated
checkConnectionWhileIdle: false
# the amount of time to sleep between runs of the idle connection validation, abandoned cleaner and idle pool resizing
evictionInterval: 10s
# the minimum amount of time an connection must sit idle in the pool before it is eligible for eviction
minIdleTime: 1 minute
We highly recommend you use JDBI’s SQL Objects API, which allows you to write DAO classes as interfaces:
public interface MyDAO {
@SqlUpdate("create table something (id int primary key, name varchar(100))")
void createSomethingTable();
@SqlUpdate("insert into something (id, name) values (:id, :name)")
void insert(@Bind("id") int id, @Bind("name") String name);
@SqlQuery("select name from something where id = :id")
String findNameById(@Bind("id") int id);
}
final MyDAO dao = database.onDemand(MyDAO.class);
This ensures your DAO classes are trivially mockable, as well as encouraging you to extract mapping
code (e.g., ResultSet
-> domain objects) into testable, reusable classes.
By adding the DBIExceptionsBundle
to your application, Dropwizard
will automatically unwrap any thrown SQLException
or DBIException
instances.
This is critical for debugging, since otherwise only the common wrapper exception’s stack trace is
logged.
If you’re using JDBI’s SQL Objects API (and you should be), dropwizard-jdbi
will
automatically prepend the SQL object’s class and method name to the SQL query as an SQL comment:
/* com.example.service.dao.UserDAO.findByName */
SELECT id, name, email
FROM users
WHERE name = 'Coda';
This will allow you to quickly determine the origin of any slow or misbehaving queries.
dropwizard-jdbi
supports Optional<T>
arguments and ImmutableList<T>
and
ImmutableSet<T>
query results.
dropwizard-jdbi
supports joda-time DateTime
arguments and DateTime
fields in query results.
The dropwizard-migrations
module provides you with a wrapper for Liquibase database
refactoring.
Like Dropwizard JDBI, your configuration class needs a
DataSourceFactory
instance:
public class ExampleConfiguration extends Configuration {
@Valid
@NotNull
private DataSourceFactory database = new DataSourceFactory();
@JsonProperty("database")
public DataSourceFactory getDataSourceFactory() {
return database;
}
}
Then, in your application’s initialize
method, add a new MigrationsBundle
subclass:
@Override
public void initialize(Bootstrap<ExampleConfiguration> bootstrap) {
bootstrap.addBundle(new MigrationsBundle<ExampleConfiguration>() {
@Override
public DataSourceFactory getDataSourceFactory(ExampleConfiguration configuration) {
return configuration.getDataSourceFactory();
}
});
}
Your database migrations are stored in your Dropwizard project, in
src/main/resources/migrations.xml
. This file will be packaged with your application, allowing you to
run migrations using your application’s command-line interface.
For example, to create a new people
table, you might create an initial migrations.xml
like
this:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet id="1" author="codahale">
<createTable tableName="people">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="fullName" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="jobTitle" type="varchar(255)"/>
</createTable>
</changeSet>
</databaseChangeLog>
For more information on available database refactorings, check the Liquibase documentation.
To check the state of your database, use the db status
command:
java -jar hello-world.jar db status helloworld.yml
If your database already has an existing schema and you’d like to pre-seed your migrations.xml
document, you can run the db dump
command:
java -jar hello-world.jar db dump helloworld.yml
This will output a Liquibase change log with a changeset capable of recreating your database.
To tag your schema at a particular point in time (e.g., to make rolling back easier), use the
db tag
command:
java -jar hello-world.jar db tag helloworld.yml 2012-10-08-pre-user-move
To apply pending changesets to your database schema, run the db migrate
command:
java -jar hello-world.jar db migrate helloworld.yml
Warning
This will potentially make irreversible changes to your database. Always check the pending DDL
scripts by using the --dry-run
flag first. This will output the SQL to be run to stdout.
Note
To apply only a specific number of pending changesets, use the --count
flag.
To roll back changesets which have already been applied, run the db rollback
command. You will
need to specify either a tag, a date, or a number of changesets to roll back to:
java -jar hello-world.jar db rollback helloworld.yml --tag 2012-10-08-pre-user-move
Warning
This will potentially make irreversible changes to your database. Always check the pending DDL
scripts by using the --dry-run
flag first. This will output the SQL to be run to stdout.
To verify that a set of pending changesets can be fully rolled back, use the db test
command,
which will migrate forward, roll back to the original state, then migrate forward again:
java -jar hello-world.jar db test helloworld.yml
Warning
Do not run this in production, for obvious reasons.
To prepare a rollback script for pending changesets before they have been applied, use the
db prepare-rollback
command:
java -jar hello-world.jar db prepare-rollback helloworld.yml
This will output a DDL script to stdout capable of rolling back all unapplied changesets.
To generate HTML documentation on the current status of the database, use the db generate-docs
command:
java -jar hello-world.jar db generate-docs helloworld.yml ~/db-docs/
To drop all objects in the database, use the db drop-all
command:
java -jar hello-world.jar db drop-all --confirm-delete-everything helloworld.yml
Warning
You need to specify the --confirm-delete-everything
flag because this command deletes
everything in the database. Be sure you want to do that first.
To mark a pending changeset as applied (e.g., after having backfilled your migrations.xml
with
db dump
), use the db fast-forward
command:
java -jar hello-world.jar db fast-forward helloworld.yml
This will mark the next pending changeset as applied. You can also use the --all
flag to mark
all pending changesets as applied.
If you are using databases supporting multiple schemas like PostgreSQL, Oracle, or H2, you can use the
optional --catalog
and --schema
arguments to specify the database catalog and schema used for the
Liquibase commands.
For more information on available commands, either use the db --help
command, or for more
detailed help on a specific command, use db <cmd> --help
.
The dropwizard-hibernate
module provides you with managed access to Hibernate, a
powerful, industry-standard object-relation mapper (ORM).
To create a managed, instrumented SessionFactory
instance, your
configuration class needs a DataSourceFactory
instance:
public class ExampleConfiguration extends Configuration {
@Valid
@NotNull
private DataSourceFactory database = new DataSourceFactory();
@JsonProperty("database")
public DataSourceFactory getDataSourceFactory() {
return database;
}
}
Then, add a HibernateBundle
instance to your application class, specifying your entity classes
and how to get a DataSourceFactory
from your configuration subclass:
private final HibernateBundle<ExampleConfiguration> hibernate = new HibernateBundle<ExampleConfiguration>(Person.class) {
@Override
public DataSourceFactory getDataSourceFactory(ExampleConfiguration configuration) {
return configuration.getDataSourceFactory();
}
};
@Override
public void initialize(Bootstrap<ExampleConfiguration> bootstrap) {
bootstrap.addBundle(hibernate);
}
@Override
public void run(ExampleConfiguration config, Environment environment) {
final UserDAO dao = new UserDAO(hibernate.getSessionFactory());
environment.jersey().register(new UserResource(dao));
}
This will create a new managed connection pool to the database, a
health check for connectivity to the database, and a new
SessionFactory
instance for you to use in your DAO classes.
Your application’s configuration file will then look like this:
database:
# the name of your JDBC driver
driverClass: org.postgresql.Driver
# the username
user: pg-user
# the password
password: iAMs00perSecrEET
# the JDBC URL
url: jdbc:postgresql://db.example.com/db-prod
# any properties specific to your JDBC driver:
properties:
charSet: UTF-8
hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
# the maximum amount of time to wait on an empty pool before throwing an exception
maxWaitForConnection: 1s
# the SQL query to run when validating a connection's liveness
validationQuery: "/* MyApplication Health Check */ SELECT 1"
# the minimum number of connections to keep open
minSize: 8
# the maximum number of connections to keep open
maxSize: 32
# whether or not idle connections should be validated
checkConnectionWhileIdle: false
Dropwizard comes with AbstractDAO
, a minimal template for entity-specific DAO classes. It
contains type-safe wrappers for most of SessionFactory
’s common operations:
public class PersonDAO extends AbstractDAO<Person> {
public PersonDAO(SessionFactory factory) {
super(factory);
}
public Person findById(Long id) {
return get(id);
}
public long create(Person person) {
return persist(person).getId();
}
public List<Person> findAll() {
return list(namedQuery("com.example.helloworld.core.Person.findAll"));
}
}
Dropwizard uses a declarative method of scoping transactional boundaries. Not all resource methods
actually require database access, so the @UnitOfWork
annotation is provided:
@GET
@Timed
@UnitOfWork
public Person findPerson(@PathParam("id") LongParam id) {
return dao.findById(id.get());
}
This will automatically open a session, begin a transaction, call findById
, commit the
transaction, and finally close the session. If an exception is thrown, the transaction is rolled
back.
Important
The Hibernate session is closed before your resource method’s return value (e.g.,
the Person
from the database), which means your resource method (or DAO) is
responsible for initializing all lazily-loaded collections, etc., before returning.
Otherwise, you’ll get a LazyInitializationException
thrown in your template (or
null
values produced by Jackson).
Dropwizard automatically configures Hibernate to prepend a comment describing the context of all queries:
/* load com.example.helloworld.core.Person */
select
person0_.id as id0_0_,
person0_.fullName as fullName0_0_,
person0_.jobTitle as jobTitle0_0_
from people person0_
where person0_.id=?
This will allow you to quickly determine the origin of any slow or misbehaving queries.
The dropwizard-auth
client provides authentication using either HTTP Basic
Authentication or OAuth2 bearer tokens.
An authenticator is a strategy class which, given a set of client-provided credentials, possibly returns a principal (i.e., the person or entity on behalf of whom your service will do something).
Authenticators implement the Authenticator<C, P extends Principal>
interface, which has a single method:
public class ExampleAuthenticator implements Authenticator<BasicCredentials, User> {
@Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
if ("secret".equals(credentials.getPassword())) {
return Optional.of(new User(credentials.getUsername()));
}
return Optional.absent();
}
}
This authenticator takes basic auth credentials and if the client-provided
password is secret
, authenticates the client as a User
with the client-provided username.
If the password doesn’t match, an absent Optional
is returned instead, indicating that the
credentials are invalid.
Warning
It’s important for authentication services not to provide too much information in their
errors. The fact that a username or email has an account may be meaningful to an
attacker, so the Authenticator
interface doesn’t allow you to distinguish between
a bad username and a bad password. You should only throw an AuthenticationException
if the authenticator is unable to check the credentials (e.g., your database is
down).
Because the backing data stores for authenticators may not handle high throughput (an RDBMS or LDAP server, for example), Dropwizard provides a decorator class which provides caching:
SimpleAuthenticator simpleAuthenticator = new SimpleAuthenticator();
CachingAuthenticator<BasicCredentials, User> cachingAuthenticator = new CachingAuthenticator<>(
metricRegistry, simpleAuthenticator,
config.getAuthenticationCachePolicy());
Dropwizard can parse Guava’s CacheBuilderSpec
from the configuration policy, allowing your
configuration file to look like this:
authenticationCachePolicy: maximumSize=10000, expireAfterAccess=10m
This caches up to 10,000 principals with an LRU policy, evicting stale entries after 10 minutes.
An authorizer is a strategy class which, given a principal and a role, decides if access is granted to the principal.
The authorizer implements the Authorizer<P extends Principal>
interface, which has a single method:
public class ExampleAuthorizer implements Authorizer<User> {
@Override
public boolean authorize(User user, String role) {
return user.getName().equals("good-guy") && role.equals("ADMIN");
}
}
The AuthDynamicFeature
with the BasicCredentialAuthFilter
and RolesAllowedDynamicFeature
enables HTTP Basic authentication and authorization; requires an authenticator which
takes instances of BasicCredentials
. If you don’t use authorization, then RolesAllowedDynamicFeature
is not required.
@Override
public void run(ExampleConfiguration configuration,
Environment environment) {
environment.jersey().register(new AuthDynamicFeature(
new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new ExampleAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setRealm("SUPER SECRET STUFF")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use @Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}
The AuthDynamicFeature
with OAuthCredentialAuthFilter
and RolesAllowedDynamicFeature
enables OAuth2 bearer-token authentication and authorization; requires an authenticator which
takes instances of String
. If you don’t use authorization, then RolesAllowedDynamicFeature
is not required.
@Override
public void run(ExampleConfiguration configuration,
Environment environment) {
environment.jersey().register(new AuthDynamicFeature(
new OAuthCredentialAuthFilter.Builder<User>()
.setAuthenticator(new ExampleOAuthAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Bearer")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use @Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}
The ChainedAuthFilter
enables usage of various authentication factories at the same time.
@Override
public void run(ExampleConfiguration configuration,
Environment environment) {
AuthFilter basicCredentialAuthFilter = new BasicCredentialAuthFilter.Builder<>()
.setAuthenticator(new ExampleBasicAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Basic")
.buildAuthFilter();
AuthFilter oauthCredentialAuthFilter = new OAuthCredentialAuthFilter.Builder<>()
.setAuthenticator(new ExampleOAuthAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Bearer")
.buildAuthFilter();
List<AuthFilter> filters = Lists.newArrayList(basicCredentialAuthFilter, oauthCredentialAuthFilter);
environment.jersey().register(new AuthDynamicFeature(new ChainedAuthFilter(filters)));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use @Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}
For this to work properly, all chained factories must produce the same type of principal, here User
.
There are two ways to protect a resource. You can mark your resource method with one of the following annotations:
@PermitAll
. All authenticated users will have access to the method.@RolesAllowed
. Access will be granted to the users with the specified roles.@DenyAll
. No access will be granted to anyone.Alternatively, you can annotate the parameter representing your principal with @Auth
. Note you must register a
jersey provider to make this work.
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
@RolesAllowed("ADMIN")
@GET
public SecretPlan getSecretPlan(@Auth User user) {
return dao.findPlanForUser(user);
}
You can also access the Principal by adding a parameter to your method @Context SecurityContext context
. Note this
will not automatically register the servlet filter which performs authentication. You will still need to add one of
@PermitAll
, @RolesAllowed
, or @DenyAll
. This is not the case with @Auth
. When that is present, the auth
filter is automatically registered to facilitate users upgrading from older versions of Dropwizard
@RolesAllowed("ADMIN")
@GET
public SecretPlan getSecretPlan(@Context SecurityContext context) {
User userPrincipal = (User) context.getUserPrincipal();
return dao.findPlanForUser(user);
}
If there are no provided credentials for the request, or if the credentials are invalid, the
provider will return a scheme-appropriate 401 Unauthorized
response without calling your
resource method.
If you have a resource which is optionally protected (e.g., you want to display a logged-in user’s name but not require login), you need to implement a custom filter which injects a security context containing the principal if it exists, without performing authentication.
Add this dependency into your pom.xml
file:
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey.version}</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
When you build your ResourceTestRule
, add the GrizzlyWebTestContainerFactory
line.
@Rule
public ResourceTestRule rule = ResourceTestRule
.builder()
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addProvider(new AuthDynamicFeature(new OAuthCredentialAuthFilter.Builder<User>()
.setAuthenticator(new MyOAuthAuthenticator())
.setAuthorizer(new MyAuthorizer())
.setRealm("SUPER SECRET STUFF")
.setPrefix("Bearer")
.buildAuthFilter()))
.addProvider(RolesAllowedDynamicFeature.class)
.addProvider(new AuthValueFactoryProvider.Binder<>(User.class))
.addResource(new ProtectedResource())
.build();
In this example, we are testing the oauth authentication, so we need to set the header manually. Note the use of resources.getJerseyTest()
to make the test work
@Test
public void testProtected() throws Exception {
final Response response = rule.getJerseyTest().target("/protected")
.request(MediaType.APPLICATION_JSON_TYPE)
.header("Authorization", "Bearer TOKEN")
.get();
assertThat(response.getStatus()).isEqualTo(200);
}
The dropwizard-forms
module provides you with a support for multi-part forms
via Jersey.
Then, in your application’s initialize
method, add a new MultiPartBundle
subclass:
@Override
public void initialize(Bootstrap<ExampleConfiguration> bootstrap) {
bootstrap.addBundle(new MultiPartBundle());
}
For additional and more detailed documentation about the Jersey multi-part support, please refer to the documentation in the Jersey User Guide and Javadoc.
Dropwizard comes with a host of validation tools out of the box to allow endpoints to return meaningful error messages when constraints are violated. Hibernate Validator is packaged with Dropwizard, so what can be done in Hibernate Validator, can be done with Dropwizard.
Almost anything can be validated on resource endpoints. To give a quick example, the following
endpoint doesn’t allow a null or empty name
query parameter.
@GET
public String find(@QueryParam("name") @NotEmpty String arg) {
// ...
}
If a client sends an empty or nonexistant name query param, Dropwizard will respond with a 400 Bad Request
code with the error: query param name may not be empty
.
Additionally, annotations such as HeaderParam
, CookieParam
, FormParam
, etc, can be
constrained with violations giving descriptive errors and 400 status codes.
If we’re accepting client-provided Person
, we probably want to ensure that the name
field of
the object isn’t null
or blank in the request. We can do this as follows:
public class Person {
@NotEmpty // ensure that name isn't null or blank
private final String name;
@JsonCreator
public Person(@JsonProperty("name") String name) {
this.name = name;
}
@JsonProperty("name")
public String getName() {
return name;
}
}
Then, in our resource class, we can add the @Valid
or the @Validated
annotation to the
Person
annotation:
@PUT
public Person replace(@Valid Person person) {
// ...
}
If the name field is missing, Dropwizard will return a 422 Unprocessable Entity
response
detailing the validation errors: name may not be empty
Note
You don’t need @Valid
or @Validated
when the type you are validating can be validated
directly (int
, String
, Integer
). If a class has fields that need validating, then
instances of the class must be marked @Valid
or @Validated
. For more information, see
the Hibernate Validator documentation on Object graphs and Cascaded validation.
Optional<T>
ConstraintsIf you have an Optional<T>
field or parameter that needs validation, add the
@UnwrapValidatedValue
annotation on it. This makes the type contained within the Optional
constrained. If the Optional
is absent, then the constraints are not applied.
Note
Be careful when using constraints with *Param
annotations with Optional<String>
types as
there is a subtle, but important distinction between null and empty. If a client requests
bar?q=
, q
will evaluate to Optional.of("")
. If you want q
to evaluate to
Optional.absent()
in this situation, change the type to NonEmptyStringParam
Note
Param types such as IntParam
and NonEmptyStringParam
can also be constrained if
annotated with @UnwrapValidatedValue
In addition to the annotations defined in Hibernate Validator, Dropwizard contains another set of annotations, which are briefly shown below.
public class Person {
@NotEmpty
private final String name;
@NotEmpty
@OneOf(value = {"m", "f"}, ignoreCase = true, ignoreWhitespace = true)
// @OneOf forces a value to value within certain values.
private final String gender;
@Max(10)
@Min(0)
// The integer contained, if present, can attain a min value of 0 and a max of 10.
private final Optional<Integer> animals;
@JsonCreator
public Person(@JsonProperty("name") String name) {
this.name = name;
}
@JsonProperty("name")
public String getName() {
return name;
}
// Method that must return true for the object to be valid
@ValidationMethod(message="name may not be Coda")
@JsonIgnore
public boolean isNotCoda() {
return !"Coda".equals(name);
}
}
The reason why Dropwizard defines @ValidationMethod
is that more complex validations (for
example, cross-field comparisons) are often hard to do using declarative annotations. Adding
@ValidationMethod
to any boolean
-returning method which begins with is
is a short and
simple workaround:
Note
Due to the rather daft JavaBeans conventions, when using @ValidationMethod
, the method must
begin with is
(e.g., #isValidPortRange()
. This is a limitation of Hibernate Validator,
not Dropwizard.
@Validated
The @Validated
annotation behaves similar to @Valid
. The difference is that the
@Validated
annotation allows for validation groups to be specifically set, instead of the
default group. This is useful when different endpoints share the same entity but may have different
requirements.
Going back to our favorite Person
class. Let’s say we initially coded it such that name
has
to be non-empty, but realized that business requirements needs the max length to be no more than 5.
Instead of blowing away our current version of our API and creating angry clients, we can accept
both versions of the API but at different endpoints.
public interface Version1Checks { }
public interface Version2Checks { }
public class Person {
@NotEmpty(groups = Version1Checks.class)
@Length(max = 5, groups = Version2Checks.class)
private String name;
@JsonCreator
public Person(@JsonProperty("name") String name) {
this.name = name;
}
@JsonProperty
public String getName() {
return name;
}
}
@Path("/person")
@Produces(MediaType.APPLICATION_JSON)
public class PersonResource {
@POST
@Path("/v1")
public void createPersonV1(@Validated(Version1Checks.class) Person person) {
}
@POST
@Path("/v2")
public void createPersonV2(@Validated({Version1Checks.class, Version2Checks.class}) Person person) {
}
}
Now, when clients hit /person/v1
the Person
entity will be checked by all the constraints
that are a part of the Version1Checks
group. If /person/v2
is hit, then all the validations
are performed.
Note
Since interfaces can inherit other interfaces, Version2Checks
can extend Version1Checks
and wherever @Validated(Version2Checks.class)
is used, version 1 constraints are checked
too.
It is critical to test the constraints so that you can ensure the assumptions about the data hold and see what kinds of error messages clients will receive for bad input. The recommended way for testing annotations is through Testing Resources, as Dropwizard does a bit of magic behind the scenes when a constraint violation occurs to set the response’s status code and ensure that the error messages are user friendly.
@Test
public void personNeedsAName() {
// Tests what happens when a person with a null name is sent to
// the endpoint.
final Response post = resources.client()
.target("/person/v1").request()
.post(Entity.json(new Person(null)));
// Clients will recieve a 422 on bad request entity
assertThat(post.getStatus()).isEqualTo(422);
// Check to make sure that errors are correct and human readable
ValidationErrorMessage msg = post.readEntity(ValidationErrorMessage.class);
assertThat(msg.getErrors())
.containsOnly("name may not be empty");
}
While Dropwizard provides good defaults for error messages, one size may not fit all and so there
are a series of extension points. To register your own
ExceptionMapper<ConstraintViolationException>
you’ll need to first set
registerDefaultExceptionMappers
to false in the configuration file or in code before registering
your exception mapper with jersey. Then, optionally, register other default exception mappers:
LoggingExceptionMapper<Throwable>
JsonProcessingExceptionMapper
EarlyEofExceptionMapper
If you need to validate entities outside of resource endpoints, the validator can be accessed in the
Environment
when the application is first ran.
Validator validator = environment.getValidator();
Set<ConstraintViolation> errors = validator.validate(/* instance of class */)
The method used to determine what status code to return based on violations is
ConstraintViolations.determineStatus
The method used to determine the human friendly error message due to a constraint violation is
ConstraintMessage.getMessage
.
The dropwizard-views-mustache
& dropwizard-views-freemarker
modules provide you with simple, fast HTML views using either FreeMarker or Mustache.
To enable views for your Application, add the ViewBundle
in the initialize
method of your Application class:
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
bootstrap.addBundle(new ViewBundle<MyConfiguration>());
}
You can pass configuration through to view renderers by overriding getViewConfiguration
:
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
bootstrap.addBundle(new ViewBundle<MyConfiguration>() {
@Override
public Map<String, Map<String, String>> getViewConfiguration(MyConfiguration config) {
return config.getViewRendererConfiguration();
}
});
}
The returned map should have, for each extension (such as .ftl
), a Map<String, String>
describing how to configure the renderer. Specific keys and their meanings can be found in the FreeMarker and Mustache documentation:
views:
.ftl:
strict_syntax: yes
Then, in your resource method, add a View
class:
public class PersonView extends View {
private final Person person;
public PersonView(Person person) {
super("person.ftl");
this.person = person;
}
public Person getPerson() {
return person;
}
}
person.ftl
is the path of the template relative to the class name. If this class was
com.example.service.PersonView
, Dropwizard would then look for the file
src/main/resources/com/example/service/person.ftl
.
If your template ends with .ftl
, it’ll be interpreted as a FreeMarker template. If it ends with
.mustache
, it’ll be interpreted as a Mustache template.
Tip
Dropwizard Freemarker Views also support localized template files. It picks up the client’s locale
from their Accept-Language
, so you can add a French template in person_fr.ftl
or a Canadian
template in person_en_CA.ftl
.
Your template file might look something like this:
<#-- @ftlvariable name="" type="com.example.views.PersonView" -->
<html>
<body>
<!-- calls getPerson().getName() and sanitizes it -->
<h1>Hello, ${person.name?html}!</h1>
</body>
</html>
The @ftlvariable
lets FreeMarker (and any FreeMarker IDE plugins you may be using) know that the
root object is a com.example.views.PersonView
instance. If you attempt to call a property which
doesn’t exist on PersonView
– getConnectionPool()
, for example – it will flag that line in
your IDE.
Once you have your view and template, you can simply return an instance of your View
subclass:
@Path("/people/{id}")
@Produces(MediaType.TEXT_HTML)
public class PersonResource {
private final PersonDAO dao;
public PersonResource(PersonDAO dao) {
this.dao = dao;
}
@GET
public PersonView getPerson(@PathParam("id") String id) {
return new PersonView(dao.find(id));
}
}
Tip
Jackson can also serialize your views, allowing you to serve both text/html
and
application/json
with a single representation class.
For more information on how to use FreeMarker, see the FreeMarker documentation.
For more information on how to use Mustache, see the Mustache and Mustache.java documentation.
If there is an error with the template (eg. the template file is not found or there is a compilation
error with the template), the user will receive a 500 Internal Sever Error
with a generic HTML
message. The exact error will logged under debug mode.
The dropwizard-scala
module is now maintained and documented elsewhere.
The dropwizard-testing
module provides you with some handy classes for testing
your representation classes
and resource classes. It also provides a JUnit rule
for full-stack testing of your entire app.
While Jackson’s JSON support is powerful and fairly easy-to-use, you shouldn’t just rely on eyeballing your representation classes to ensure you’re producing the API you think you are. By using the helper methods in FixtureHelpers, you can add unit tests for serializing and deserializing your representation classes to and from JSON.
Let’s assume we have a Person
class which your API uses as both a request entity (e.g., when
writing via a PUT
request) and a response entity (e.g., when reading via a GET
request):
public class Person {
private String name;
private String email;
private Person() {
// Jackson deserialization
}
public Person(String name, String email) {
this.name = name;
this.email = email;
}
@JsonProperty
public String getName() {
return name;
}
@JsonProperty
public void setName(String name) {
this.name = name;
}
@JsonProperty
public String getEmail() {
return email;
}
@JsonProperty
public void setEmail(String email) {
this.email = email;
}
// hashCode
// equals
// toString etc.
}
First, write out the exact JSON representation of a Person
in the
src/test/resources/fixtures
directory of your Dropwizard project as person.json
:
{
"name": "Luther Blissett",
"email": "lb@example.com"
}
Next, write a test for serializing a Person
instance to JSON:
import static io.dropwizard.testing.FixtureHelpers.*;
import static org.assertj.core.api.Assertions.assertThat;
import io.dropwizard.jackson.Jackson;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
public class PersonTest {
private static final ObjectMapper MAPPER = Jackson.newObjectMapper();
@Test
public void serializesToJSON() throws Exception {
final Person person = new Person("Luther Blissett", "lb@example.com");
final String expected = MAPPER.writeValueAsString(
MAPPER.readValue(fixture("fixtures/person.json"), Person.class));
assertThat(MAPPER.writeValueAsString(person)).isEqualTo(expected);
}
}
This test uses AssertJ assertions and JUnit to test that when a Person
instance is serialized
via Jackson it matches the JSON in the fixture file. (The comparison is done on a normalized JSON
string representation, so formatting doesn’t affect the results.)
Next, write a test for deserializing a Person
instance from JSON:
import static io.dropwizard.testing.FixtureHelpers.*;
import static org.assertj.core.api.Assertions.assertThat;
import io.dropwizard.jackson.Jackson;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
public class PersonTest {
private static final ObjectMapper MAPPER = Jackson.newObjectMapper();
@Test
public void deserializesFromJSON() throws Exception {
final Person person = new Person("Luther Blissett", "lb@example.com");
assertThat(MAPPER.readValue(fixture("fixtures/person.json"), Person.class))
.isEqualTo(person);
}
}
This test uses AssertJ assertions and JUnit to test that when a Person
instance is
deserialized via Jackson from the specified JSON fixture it matches the given object.
While many resource classes can be tested just by calling the methods on the class in a test, some
resources lend themselves to a more full-stack approach. For these, use ResourceTestRule
, which
loads a given resource instance in an in-memory Jersey server:
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
public class PersonResourceTest {
private static final PeopleStore dao = mock(PeopleStore.class);
@ClassRule
public static final ResourceTestRule resources = ResourceTestRule.builder()
.addResource(new PersonResource(dao))
.build();
private final Person person = new Person("blah", "blah@example.com");
@Before
public void setup() {
when(dao.fetchPerson(eq("blah"))).thenReturn(person);
}
@After
public void tearDown(){
// we have to reset the mock after each test because of the
// @ClassRule, or use a @Rule as mentioned below.
reset(dao);
}
@Test
public void testGetPerson() {
assertThat(resources.client().target("/person/blah").request().get(Person.class))
.isEqualTo(person);
verify(dao).fetchPerson("blah");
}
}
Instantiate a ResourceTestRule
using its Builder
and add the various resource instances you
want to test via ResourceTestRule.Builder#addResource(Object)
. Use a @ClassRule
annotation
to have the rule wrap the entire test class or the @Rule
annotation to have the rule wrap
each test individually (make sure to remove static final modifier from resources
).
In your tests, use #client()
, which returns a Jersey Client
instance to talk to and test
your instances.
This doesn’t require opening a port, but ResourceTestRule
tests will perform all the serialization,
deserialization, and validation that happens inside of the HTTP process.
This also doesn’t require a full integration test. In the above
example, a mocked PeopleStore
is passed to the
PersonResource
instance to isolate it from the database. Not only does this make the test much
faster, but it allows your resource unit tests to test error conditions and edge cases much more
easily.
Hint
You can trust PeopleStore
works because you’ve got working unit tests for it, right?
Note that the in-memory Jersey test container does not support all features, such as the @Context
injection used by
BasicAuthFactory
and OAuthFactory
. A different test container can be used via
ResourceTestRule.Builder#setTestContainerFactory(TestContainerFactory)
.
For example, if you want to use the Grizzly HTTP server (which supports @Context
injections) you need to add the
dependency for the Jersey Test Framework providers to your Maven POM and set GrizzlyWebTestContainerFactory
as
TestContainerFactory
in your test classes.
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
public class ResourceTestWithGrizzly {
@ClassRule
public static final ResourceTestRule RULE = ResourceTestRule.builder()
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new ExampleResource())
.build();
@Test
public void testResource() {
assertThat(RULE.getJerseyTest().target("/example").request()
.get(String.class))
.isEqualTo("example");
}
}
To avoid circular dependencies in your projects or to speed up test runs, you can test your HTTP client code
by writing a JAX-RS resource as test double and let the DropwizardClientRule
start and stop a simple Dropwizard
application containing your test doubles.
public class CustomClientTest {
@Path("/ping")
public static class PingResource {
@GET
public String ping() {
return "pong";
}
}
@ClassRule
public static final DropwizardClientRule dropwizard = new DropwizardClientRule(new PingResource());
@Test
public void shouldPing() throws IOException {
final URL url = new URL(dropwizard.baseUri() + "/ping");
final String response = new BufferedReader(new InputStreamReader(url.openStream())).readLine();
assertEquals("pong", response);
}
}
Hint
Of course you would use your HTTP client in the @Test
method and not java.net.URL#openStream()
.
The DropwizardClientRule
takes care of:
It can be useful to start up your entire app and hit it with real HTTP requests during testing.
Adding DropwizardAppRule
to your JUnit test class will start the app prior to any tests
running and stop it again when they’ve completed (roughly equivalent to having used @BeforeClass
and @AfterClass
).
DropwizardAppRule
also exposes the app’s Configuration
,
Environment
and the app object itself so that these can be queried by the tests.
public class LoginAcceptanceTest {
@ClassRule
public static final DropwizardAppRule<TestConfiguration> RULE =
new DropwizardAppRule<TestConfiguration>(MyApp.class, ResourceHelpers.resourceFilePath("my-app-config.yaml"));
@Test
public void loginHandlerRedirectsAfterPost() {
Client client = new JerseyClientBuilder(RULE.getEnvironment()).build("test client");
Response response = client.target(
String.format("http://localhost:%d/login", RULE.getLocalPort()))
.request()
.post(Entity.json(loginForm()));
assertThat(response.getStatus()).isEqualTo(302);
}
}
By creating a DropwizardTestSupport instance in your test you can manually start and stop the app in your tests, you do this by calling its before
and after
methods. DropwizardTestSupport
also exposes the app’s Configuration
, Environment
and the app object itself so that these can be queried by the tests.
public class LoginAcceptanceTest {
public static final DropwizardTestSupport<TestConfiguration> SUPPORT =
new DropwizardTestSupport<TestConfiguration>(MyApp.class, ResourceHelpers.resourceFilePath("my-app-config.yaml"));
@BeforeClass
public void beforeClass() {
SUPPORT.before();
}
@AfterClass
public void afterClass() {
SUPPORT.after();
}
@Test
public void loginHandlerRedirectsAfterPost() {
Client client = new JerseyClientBuilder(SUPPORT.getEnvironment()).build("test client");
Response response = client.target(
String.format("http://localhost:%d/login", RULE.getLocalPort()))
.request()
.post(Entity.json(loginForm()));
assertThat(response.getStatus()).isEqualTo(302);
}
}
The dropwizard-example
module provides you with a working Dropwizard Example Application.
Preconditions:
Preparations to start the Dropwizard Example Application:
mvn clean install
java -jar target/dropwizard-example-0.9.0.jar db migrate example.yml
Starting the Dropwizard Example Application:
java -jar target/dropwizard-example-0.9.0.jar server example.yml
com.example.helloworld.HelloWorldApplication server example.yml
Working with the Dropwizard Example Application:
curl -H "Content-Type: application/json" -X POST -d '{"fullName":"John Doe", "jobTitle" : "Chief Wizard" }' http://localhost:8080/people
curl http://localhost:8080/people/1
http://localhost:8080/people/1/view_freemarker
http://localhost:8080/people/1/view_mustache
server:
type: default
maxThreads: 1024
Name | Default | Description |
---|---|---|
type | default |
|
maxThreads | 1024 | The maximum number of threads to use for requests. |
minThreads | 8 | The minimum number of threads to use for requests. |
maxQueuedRequests | 1024 | The maximum number of requests to queue before blocking the acceptors. |
idleThreadTimeout | 1 minute | The amount of time a worker thread can be idle before being stopped. |
nofileSoftLimit | (none) | The number of open file descriptors before a soft error is issued.
Requires Jetty’s libsetuid.so on java.library.path . |
nofileHardLimit | (none) | The number of open file descriptors before a hard error is issued.
Requires Jetty’s libsetuid.so on java.library.path . |
gid | (none) | The group ID to switch to once the connectors have started.
Requires Jetty’s libsetuid.so on java.library.path . |
uid | (none) | The user ID to switch to once the connectors have started.
Requires Jetty’s libsetuid.so on java.library.path . |
user | (none) | The username to switch to once the connectors have started.
Requires Jetty’s libsetuid.so on java.library.path . |
group | (none) | The group to switch to once the connectors have started.
Requires Jetty’s libsetuid.so on java.library.path . |
umask | (none) | The umask to switch to once the connectors have started.
Requires Jetty’s libsetuid.so on java.library.path . |
startsAsRoot | (none) | Whether or not the Dropwizard application is started as a root user.
Requires Jetty’s libsetuid.so on java.library.path . |
shutdownGracePeriod | 30 seconds | The maximum time to wait for Jetty, and all Managed instances, to cleanly shutdown before forcibly terminating them. |
allowedMethods | GET , POST , PUT , DELETE ,
HEAD , OPTIONS , PATCH |
The set of allowed HTTP methods. Others will be rejected with a 405 Method Not Allowed response. |
rootPath | /* |
the JAX-RS resources will be served. |
registerDefaultExceptionMappers | true | Whether or not the default Jersey ExceptionMappers should be registered. Set this to false if you want to register your own. |
server:
gzip:
bufferSize: 8KiB
Name | Default | Description |
---|---|---|
enabled | true | If true, all requests with gzip or deflate in the Accept-Encoding header will have their
response entities compressed and requests with gzip or deflate in the Content-Encoding
header will have their request entities decompressed. |
minimumEntitySize | 256 bytes | All response entities under this size are not compressed. |
bufferSize | 8KiB | The size of the buffer to use when compressing. |
excludedUserAgents | [] | The set of user agents to exclude from compression. |
excludedUserAgentPatterns | [] | The set of user agent patterns to exclude from compression. |
compressedMimeTypes | Jetty’s default | The list of mime types to compress. The default is all types apart the commonly known image, video, audio and compressed types. |
includedMethods | Jetty’s default | The list list of HTTP methods to compress. The default is to compress only GET responses. |
deflateCompressionLevel | -1 | The compression level used for ZLIB deflation(compression). |
gzipCompatibleDeflation | true | If true, then ZLIB deflation(compression) will be performed in the GZIP-compatible mode. |
gzipCompatibleInflation | true | If true, then ZLIB inflation(decompression) will be performed in the GZIP-compatible mode. |
vary | Accept-Encoding |
Value of the Vary header sent with responses that could be compressed. |
server:
requestLog:
timeZone: UTC
Name | Default | Description |
---|---|---|
timeZone | UTC | The time zone to which request timestamps will be converted. |
appenders | console appender | The set of AppenderFactory appenders to which requests will be logged. TODO See logging/appender refs for more info |
Extends the attributes that are available to all servers
server:
type: simple
applicationContextPath: /application
adminContextPath: /admin
connector:
type: http
port: 8080
Name | Default | Description |
---|---|---|
connector | http connector | HttpConnectorFactory HTTP connector listening on port 8080. The ConnectorFactory connector which will handle both application and admin requests. TODO link to connector below. |
applicationContextPath | /application | The context path of the application servlets, including Jersey. |
adminContextPath | /admin | The context path of the admin servlets, including metrics and tasks. |
Extends the attributes that are available to all servers
server:
adminMinThreads: 1
adminMaxThreads: 64
adminContextPath: /
applicationContextPath: /
applicationConnectors:
- type: http
port: 8080
- type: https
port: 8443
keyStorePath: example.keystore
keyStorePassword: example
validateCerts: false
adminConnectors:
- type: http
port: 8081
- type: https
port: 8444
keyStorePath: example.keystore
keyStorePassword: example
validateCerts: false
Name | Default | Description |
---|---|---|
applicationConnectors | An HTTP connector listening on port 8080. | A set of connectors which will handle application requests. |
adminConnectors | An HTTP connector listening on port 8081. | An HTTP connector listening on port 8081. A set of connectors which will handle admin requests. |
adminMinThreads | 1 | The minimum number of threads to use for admin requests. |
adminMaxThreads | 64 | The maximum number of threads to use for admin requests. |
adminContextPath | / | The context path of the admin servlets, including metrics and tasks. |
applicationContextPath | / | The context path of the application servlets, including Jersey. |
# Extending from the default server configuration
server:
applicationConnectors:
- type: http
port: 8080
bindHost: 127.0.0.1 # only bind to loopback
headerCacheSize: 512 bytes
outputBufferSize: 32KiB
maxRequestHeaderSize: 8KiB
maxResponseHeaderSize: 8KiB
inputBufferSize: 8KiB
idleTimeout: 30 seconds
minBufferPoolSize: 64 bytes
bufferPoolIncrement: 1KiB
maxBufferPoolSize: 64KiB
acceptorThreads: 1
selectorThreads: 2
acceptQueueSize: 1024
reuseAddress: true
soLingerTime: 345s
useServerHeader: false
useDateHeader: true
useForwardedHeaders: true
Name | Default | Description |
---|---|---|
port | 8080 | The TCP/IP port on which to listen for incoming connections. |
bindHost | (none) | The hostname to bind to. |
headerCacheSize | 512 bytes | The size of the header field cache. |
outputBufferSize | 32KiB | The size of the buffer into which response content is aggregated before being sent to the client. A larger buffer can improve performance by allowing a content producer to run without blocking, however larger buffers consume more memory and may induce some latency before a client starts processing the content. |
maxRequestHeaderSize | 8KiB | The maximum size of a request header. Larger headers will allow for more and/or larger cookies plus larger form content encoded in a URL. However, larger headers consume more memory and can make a server more vulnerable to denial of service attacks. |
maxResponseHeaderSize | 8KiB | The maximum size of a response header. Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection). However, larger headers will also consume more memory. |
inputBufferSize | 8KiB | The size of the per-connection input buffer. |
idleTimeout | 30 seconds | The maximum idle time for a connection, which roughly translates to the java.net.Socket#setSoTimeout(int) call, although with NIO implementations other mechanisms may be used to implement the timeout. The max idle time is applied when waiting for a new message to be received on a connection or when waiting for a new message to be sent on a connection. This value is interpreted as the maximum time between some progress being made on the connection. So if a single byte is read or written, then the timeout is reset. |
minBufferPoolSize | 64 bytes | The minimum size of the buffer pool. |
bufferPoolIncrement | 1KiB | The increment by which the buffer pool should be increased. |
maxBufferPoolSize | 64KiB | The maximum size of the buffer pool. |
acceptorThreads | # of CPUs/2 | The number of worker threads dedicated to accepting connections. |
selectorThreads | # of CPUs | The number of worker threads dedicated to sending and receiving data. |
acceptQueueSize | (OS default) | The size of the TCP/IP accept queue for the listening socket. |
reuseAddress | true | Whether or not SO_REUSEADDR is enabled on the listening socket. |
soLingerTime | (disabled) | Enable/disable SO_LINGER with the specified linger time. |
useServerHeader | false | Whether or not to add the Server header to each response. |
useDateHeader | true | Whether or not to add the Date header to each response. |
useForwardedHeaders | true | Whether or not to look at X-Forwarded-* headers added by proxies. See
ForwardedRequestCustomizer for details. |
Extends the attributes that are available to the HTTP connector
# Extending from the default server configuration
server:
applicationConnectors:
- type: https
port: 8443
....
keyStorePath: /path/to/file
keyStorePassword: changeit
keyStoreType: JKS
keyStoreProvider:
trustStorePath: /path/to/file
trustStorePassword: changeit
trustStoreType: JKS
trustStoreProvider:
keyManagerPassword: changeit
needClientAuth: false
wantClientAuth:
certAlias: <alias>
crlPath: /path/to/file
enableCRLDP: false
enableOCSP: false
maxCertPathLength: (unlimited)
ocspResponderUrl: (none)
jceProvider: (none)
validateCerts: true
validatePeers: true
supportedProtocols: SSLv3
excludedProtocols: (none)
supportedCipherSuites: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
excludedCipherSuites: (none)
allowRenegotiation: true
endpointIdentificationAlgorithm: (none)
Name | Default | Description |
---|---|---|
keyStorePath | REQUIRED | The path to the Java key store which contains the host certificate and private key. |
keyStorePassword | REQUIRED | The password used to access the key store. |
keyStoreType | JKS | The type of key store (usually JKS , PKCS12 , JCEKS``,
Windows-MY }, or Windows-ROOT ). |
keyStoreProvider | (none) | The JCE provider to use to access the key store. |
trustStorePath | (none) | The path to the Java key store which contains the CA certificates used to establish trust. |
trustStorePassword | (none) | The password used to access the trust store. |
trustStoreType | JKS | The type of trust store (usually JKS , PKCS12 , JCEKS ,
Windows-MY , or Windows-ROOT ). |
trustStoreProvider | (none) | The JCE provider to use to access the trust store. |
keyManagerPassword | (none) | The password, if any, for the key manager. |
needClientAuth | (none) | Whether or not client authentication is required. |
wantClientAuth | (none) | Whether or not client authentication is requested. |
certAlias | (none) | The alias of the certificate to use. |
crlPath | (none) | The path to the file which contains the Certificate Revocation List. |
enableCRLDP | false | Whether or not CRL Distribution Points (CRLDP) support is enabled. |
enableOCSP | false | Whether or not On-Line Certificate Status Protocol (OCSP) support is enabled. |
maxCertPathLength | (unlimited) | The maximum certification path length. |
ocspResponderUrl | (none) | The location of the OCSP responder. |
jceProvider | (none) | The name of the JCE provider to use for cryptographic support. |
validateCerts | true | Whether or not to validate TLS certificates before starting. If enabled, Dropwizard will refuse to start with expired or otherwise invalid certificates. |
validatePeers | true | Whether or not to validate TLS peer certificates. |
supportedProtocols | (none) | A list of protocols (e.g., SSLv3 , TLSv1 ) which are supported. All
other protocols will be refused. |
excludedProtocols | (none) | A list of protocols (e.g., SSLv3 , TLSv1 ) which are excluded. These
protocols will be refused. |
supportedCipherSuites | (none) | A list of cipher suites (e.g., TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 ) which
are supported. All other cipher suites will be refused |
excludedCipherSuites | (none) | A list of cipher suites (e.g., TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 ) which
are excluded. These cipher suites will be refused and exclusion takes higher
precedence than inclusion, such that if a cipher suite is listed in
supportedCipherSuites and excludedCipherSuites , the cipher suite will be
excluded. To verify that the proper cipher suites are being whitelisted and
blacklisted, it is recommended to use the tool sslyze. |
allowRenegotiation | true | Whether or not TLS renegotiation is allowed. |
endpointIdentificationAlgorithm | (none) | Which endpoint identification algorithm, if any, to use during the TLS handshake. |
Extends the attributes that are available to the HTTPS connector
For this connector to work with ALPN protocol you need to provide alpn-boot library to JVM’s bootpath. The correct library version depends on the JVM version. Consult Jetty ALPN guide for the reference.
server:
applicationConnectors:
- type: spdy3
port: 8445
keyStorePath: example.keystore
keyStorePassword: example
validateCerts: false
Name | Default | Description |
---|---|---|
pushStrategy | (none) | The push strategy to use for server-initiated SPDY pushes. |
logging:
level: INFO
loggers:
"io.dropwizard": INFO
"org.hibernate.SQL":
level: DEBUG
additive: false
appenders:
- type: file
currentLogFilename: /var/log/myapplication-sql.log
archivedLogFilenamePattern: /var/log/myapplication-sql-%d.log.gz
archivedFileCount: 5
appenders:
- type: console
Name | Default | Description |
---|---|---|
level | Level.INFO | Logback logging level. |
additive | true | Logback additive setting. |
loggers | (none) | Individual logger configuration (both forms are acceptable). |
appenders | (none) | One of console, file or syslog. |
logging:
level: INFO
appenders:
- type: console
threshold: ALL
timeZone: UTC
target: stdout
logFormat: # TODO
Name | Default | Description |
---|---|---|
type | REQUIRED | The appender type. Must be console . |
threshold | ALL | The lowest level of events to print to the console. |
timeZone | UTC | The time zone to which event timestamps will be converted. |
target | stdout | The name of the standard stream to which events will be written.
Can be stdout or stderr . |
logFormat | default | The Logback pattern with which events will be formatted. See the Logback documentation for details. |
logging:
level: INFO
appenders:
- type: file
currentLogFilename: /var/log/myapplication.log
threshold: ALL
archive: true
archivedLogFilenamePattern: /var/log/myapplication-%d.log
archivedFileCount: 5
timeZone: UTC
logFormat: # TODO
Name | Default | Description |
---|---|---|
type | REQUIRED | The appender type. Must be file . |
currentLogFilename | REQUIRED | The filename where current events are logged. |
threshold | ALL | The lowest level of events to write to the file. |
archive | true | Whether or not to archive old events in separate files. |
archivedLogFilenamePattern | (none) | Required if archive is true .
The filename pattern for archived files. %d is replaced with the date in yyyy-MM-dd form,
and the fact that it ends with .gz indicates the file will be gzipped as it’s archived.
Likewise, filename patterns which end in .zip will be filled as they are archived. |
archivedFileCount | 5 | The number of archived files to keep. Must be between 1 and 50 . |
timeZone | UTC | The time zone to which event timestamps will be converted. |
logFormat | default | The Logback pattern with which events will be formatted. See the Logback documentation for details. |
logging:
level: INFO
appenders:
- type: syslog
host: localhost
port: 514
facility: local0
threshold: ALL
stackTracePrefix: \t
logFormat: # TODO
Name | Default | Description |
---|---|---|
host | localhost | The hostname of the syslog server. |
port | 514 | The port on which the syslog server is listening. |
facility | local0 | The syslog facility to use. Can be either auth , authpriv ,
daemon , cron , ftp , lpr , kern , mail ,
news , syslog , user , uucp , local0 ,
local1 , local2 , local3 , local4 , local5 ,
local6 , or local7 . |
threshold | ALL | The lowest level of events to write to the file. |
logFormat | default | The Logback pattern with which events will be formatted. See the Logback documentation for details. |
stackTracePrefix | t | The prefix to use when writing stack trace lines (these are sent to the syslog server separately from the main message) |
The metrics configuration has two fields; frequency and reporters.
metrics:
frequency: 1 minute
reporters:
- type: <type>
Name | Default | Description |
---|---|---|
frequency | 1 minute | The frequency to report metrics. Overridable per-reporter. |
reporters | (none) | A list of reporters to report metrics. |
The following options are available for all metrics reporters.
metrics:
reporters:
- type: <type>
durationUnit: milliseconds
rateUnit: seconds
excludes: (none)
includes: (all)
useRegexFilters: false
frequency: 1 minute
Name | Default | Description |
---|---|---|
durationUnit | milliseconds | The unit to report durations as. Overrides per-metric duration units. |
rateUnit | seconds | The unit to report rates as. Overrides per-metric rate units. |
excludes | (none) | Metrics to exclude from reports, by name. When defined, matching metrics will not be reported. |
includes | (all) | Metrics to include in reports, by name. When defined, only these metrics will be reported. |
useRegexFilters | false | Indicates whether the values of the ‘includes’ and ‘excludes’ fields should be treated as regular expressions or not. |
frequency | (none) | The frequency to report metrics. Overrides the default. |
The inclusion and exclusion rules are defined as:
the exclusion rules it will not be included in reports even if it also matches the inclusion rules.
These options are available only to “formatted” reporters and extend the options available to all reporters
metrics:
reporters:
- type: <type>
locale: <system default>
Name | Default | Description |
---|---|---|
locale | System default | The Locale for formatting numbers, dates and times. |
Reports metrics periodically to the console.
Extends the attributes that are available to formatted reporters
metrics:
reporters:
- type: console
timeZone: UTC
output: stdout
Name | Default | Description |
---|---|---|
timeZone | UTC | The timezone to display dates/times for. |
output | stdout | The stream to write to. One of stdout or stderr . |
Reports metrics periodically to a CSV file.
Extends the attributes that are available to formatted reporters
metrics:
reporters:
- type: csv
file: /path/to/file
Name | Default | Description |
---|---|---|
file | No default | The CSV file to write metrics to. |
Reports metrics periodically to Ganglia.
Extends the attributes that are available to all reporters
Note
You will need to add dropwizard-metrics-ganglia
to your POM.
metrics:
reporters:
- type: ganglia
host: localhost
port: 8649
mode: unicast
ttl: 1
uuid: (none)
spoof: localhost:8649
tmax: 60
dmax: 0
Name | Default | Description |
---|---|---|
host | localhost | The hostname (or group) of the Ganglia server(s) to report to. |
port | 8649 | The port of the Ganglia server(s) to report to. |
mode | unicast | The UDP addressing mode to announce the metrics with. One of unicast
or multicast . |
ttl | 1 | The time-to-live of the UDP packets for the announced metrics. |
uuid | (none) | The UUID to tag announced metrics with. |
spoof | (none) | The hostname and port to use instead of this nodes for the announced metrics.
In the format hostname:port . |
tmax | 60 | The tmax value to announce metrics with. |
dmax | 0 | The dmax value to announce metrics with. |
Reports metrics periodically to Graphite.
Extends the attributes that are available to all reporters
Note
You will need to add dropwizard-metrics-graphite
to your POM.
metrics:
reporters:
- type: graphite
host: localhost
port: 8080
prefix: <prefix>
Name | Default | Description |
---|---|---|
host | localhost | The hostname of the Graphite server to report to. |
port | 8080 | The port of the Graphite server to report to. |
prefix | (none) | The prefix for Metric key names to report to Graphite. |
Reports metrics periodically by logging via SLF4J.
Extends the attributes that are available to all reporters
See BaseReporterFactory and BaseFormattedReporterFactory for more options.
metrics:
reporters:
- type: log
logger: metrics
markerName: <marker name>
Name | Default | Description |
---|---|---|
logger | metrics | The name of the logger to write metrics to. |
markerName | (none) | The name of the marker to mark logged metrics with. |
See HttpClientConfiguration for more options.
httpClient:
timeout: 500ms
connectionTimeout: 500ms
timeToLive: 1h
cookiesEnabled: false
maxConnections: 1024
maxConnectionsPerRoute: 1024
keepAlive: 0ms
retries: 0
userAgent: <application name> (<client name>)
Name | Default | Description |
---|---|---|
timeout | 500 milliseconds | The maximum idle time for a connection, once established. |
connectionTimeout | 500 milliseconds | The maximum time to wait for a connection to open. |
connectionRequestTimeout | 500 milliseconds | The maximum time to wait for a connection to be returned from the connection pool. |
timeToLive | 1 hour | The maximum time a pooled connection can stay idle (not leased to any thread) before it is shut down. |
cookiesEnabled | false | Whether or not to enable cookies. |
maxConnections | 1024 | The maximum number of concurrent open connections. |
maxConnectionsPerRoute | 1024 | The maximum number of concurrent open connections per route. |
keepAlive | 0 milliseconds | The maximum time a connection will be kept alive before it is reconnected. If set to 0, connections will be immediately closed after every request/response. |
retries | 0 | The number of times to retry failed requests. Requests are only
retried if they throw an exception other than InterruptedIOException ,
UnknownHostException , ConnectException , or SSLException . |
userAgent | applicationName (clientName ) |
The User-Agent to send with requests. |
validateAfterInactivityPeriod | 0 milliseconds | The maximum time before a persistent connection is checked to remain active. If set to 0, no inactivity check will be performed. |
httpClient:
proxy:
host: 192.168.52.11
port: 8080
scheme : http
auth:
username: secret
password: stuff
nonProxyHosts:
- localhost
- '192.168.52.*'
- '*.example.com'
Name | Default | Description |
---|---|---|
host | REQUIRED | The proxy server host name or ip address. |
port | (scheme default) | The proxy server port. If the port is not set then the scheme default port is used. |
scheme | http | The proxy server URI scheme. HTTP and HTTPS schemas are permitted. By default HTTP scheme is used. |
auth | (none) | The proxy server BASIC authentication credentials. If they are not set then no credentials will be passed to the server. |
username | REQUIRED | The username used to connect to the server. |
password | REQUIRED | The password used to connect to the server. |
nonProxyHosts | (none) | List of patterns of hosts that should be reached without proxy. The patterns may contain symbol ‘*’ as a wildcard. If a host matches one of the patterns it will be reached through a direct connection. |
httpClient:
tls:
protocol: TLSv1.2
verifyHostname: true
keyStorePath: /path/to/file
keyStorePassword: changeit
keyStoreType: JKS
trustStorePath: /path/to/file
trustStorePassword: changeit
trustStoreType: JKS
trustSelfSignedCertificates: false
supportedProtocols: TLSv1.1,TLSv1.2
supportedCipherSuites: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
Name | Default | Description |
---|---|---|
protocol | TLSv1.2 | The default protocol the client will attempt to use during the SSL Handshake. See here for more information. |
verifyHostname | true | Whether to verify the hostname of the server against the hostname presented in the server certificate. |
keyStorePath | (none) | The path to the Java key store which contains the client certificate and private key. |
keyStorePassword | (none) | The password used to access the key store. |
keyStoreType | JKS | The type of key store (usually JKS , PKCS12 , JCEKS , Windows-MY , or Windows-ROOT ). |
trustStorePath | (none) | The path to the Java key store which contains the CA certificates used to establish trust. |
trustStorePassword | (none) | The password used to access the trust store. |
trustStoreType | JKS | The type of trust store (usually JKS , PKCS12 , JCEKS , Windows-MY , or Windows-ROOT ). |
trustSelfSignedCertificates | false | Whether the client will trust certificates of servers that are self-signed. |
supportedProtocols | (none) | A list of protocols (e.g., SSLv3 , TLSv1 ) which are supported. All
other protocols will be refused. |
supportedCipherSuites | (none) | A list of cipher suites (e.g., TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 ) which
are supported. All other cipher suites will be refused. |
Extends the attributes that are available to http clients
See JerseyClientConfiguration and HttpClientConfiguration for more options.
jerseyClient:
minThreads: 1
maxThreads: 128
workQueueSize: 8
gzipEnabled: true
gzipEnabledForRequests: true
chunkedEncodingEnabled: true
Name | Default | Description |
---|---|---|
minThreads | 1 | The minimum number of threads in the pool used for asynchronous requests. |
maxThreads | 128 | The maximum number of threads in the pool used for asynchronous requests. |
workQueueSize | 8 | The size of the work queue of the pool used for asynchronous requests. Additional threads will be spawn only if the queue is reached its maximum size. |
gzipEnabled | true | Adds an Accept-Encoding: gzip header to all requests, and enables automatic gzip decoding of responses. |
gzipEnabledForRequests | true | Adds a Content-Encoding: gzip header to all requests, and enables automatic gzip encoding of requests. |
chunkedEncodingEnabled | true | Enables the use of chunked encoding for requests. |
database:
driverClass : org.postgresql.Driver
url: 'jdbc:postgresql://db.example.com/db-prod'
user: pg-user
password: iAMs00perSecrEET
Name | Default | Description |
---|---|---|
driverClass | REQUIRED | The full name of the JDBC driver class. |
url | REQUIRED | The URL of the server. |
user | none | The username used to connect to the server. |
password | none | The password used to connect to the server. |
removeAbandoned | false | Remove abandoned connections if they exceed removeAbandonedTimeout. If set to true a connection is considered abandoned and eligible for removal if it has been in use longer than the removeAbandonedTimeout and the condition for abandonWhenPercentageFull is met. |
removeAbandonedTimeout | 60 seconds | The time before a database connection can be considered abandoned. |
abandonWhenPercentageFull | 0 | Connections that have been abandoned (timed out) won’t get closed and reported up unless the number of connections in use are above the percentage defined by abandonWhenPercentageFull. The value should be between 0-100. |
alternateUsernamesAllowed | false | Set to true if the call getConnection(username,password) is allowed. This is used for when the pool is used by an application accessing multiple schemas. There is a performance impact turning this option on, even when not used. |
commitOnReturn | false | Set to true if you want the connection pool to commit any pending transaction when a connection is returned. |
autoCommitByDefault | JDBC driver’s default | The default auto-commit state of the connections. |
readOnlyByDefault | JDBC driver’s default | The default read-only state of the connections. |
properties | none | Any additional JDBC driver parameters. |
defaultCatalog | none | The default catalog to use for the connections. |
defaultTransactionIsolation | JDBC driver’s default | The default transaction isolation to use for the connections. Can be one of none, default, read-uncommitted, read-committed, repeatable-read, or serializable. |
useFairQueue | true | If true, calls to getConnection are handled in a FIFO manner. |
initialSize | 10 | The initial size of the connection pool. |
minSize | 10 | The minimum size of the connection pool. |
maxSize | 100 | The maximum size of the connection pool. |
initializationQuery | none | A custom query to be run when a connection is first created. |
logAbandonedConnections | false | If true, logs stack traces of abandoned connections. |
logValidationErrors | false | If true, logs errors when connections fail validation. |
maxConnectionAge | none | If set, connections which have been open for longer than maxConnectionAge are closed when returned. |
maxWaitForConnection | 30 seconds | If a request for a connection is blocked for longer than this period, an exception will be thrown. |
minIdleTime | 1 minute | The minimum amount of time an connection must sit idle in the pool before it is eligible for eviction. |
validationQuery | SELECT 1 | The SQL query that will be used to validate connections from this pool before returning them to the caller or pool. If specified, this query does not have to return any data, it just can’t throw a SQLException. |
validationQueryTimeout | none | The timeout before a connection validation queries fail. |
checkConnectionWhileIdle | true | Set to true if query validation should take place while the connection is idle. |
checkConnectionOnBorrow | false | Whether or not connections will be validated before being borrowed from the pool. If the connection fails to validate, it will be dropped from the pool, and another will be borrowed. |
checkConnectionOnConnect | false | Whether or not connections will be validated before being added to the pool. If the connection fails to validate, it won’t be added to the pool. |
checkConnectionOnReturn | false | Whether or not connections will be validated after being returned to the pool. If the connection fails to validate, it will be dropped from the pool. |
autoCommentsEnabled | true | Whether or not ORMs should automatically add comments. |
evictionInterval | 5 seconds | The amount of time to sleep between runs of the idle connection validation, abandoned cleaner and idle pool resizing. |
validationInterval | 30 seconds | To avoid excess validation, only run validation once every interval. |
validatorClassName | none | Name of a class of a custom validator implementation, which will be used for validating connections. |
The dropwizard-configuration
module provides you with a polymorphic configuration
mechanism, meaning that a particular section of your configuration file can be implemented
using one or more configuration classes.
To use this capability for your own configuration classes, create a top-level configuration interface or class that
implements Discoverable
and add the name of that class to META-INF/services/io.dropwizard.jackson.Discoverable
.
Make sure to use Jackson polymorphic deserialization annotations appropriately.
@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
interface WidgetFactory extends Discoverable {
Widget createWidget();
}
Then create subtypes of the top-level type corresponding to each alternative, and add their names to
META-INF/services/WidgetFactory
.
@JsonTypeName("hammer")
public class HammerFactory implements WidgetFactory {
@JsonProperty
private int weight = 10;
@Override
public Hammer createWidget() {
return new Hammer(weight);
}
}
@JsonTypeName("chisel")
public class ChiselFactory implements WidgetFactory {
@JsonProperty
private float radius = 1;
@Override
public Chisel createWidget() {
return new Chisel(radius);
}
}
Now you can use WidgetFactory
objects in your application’s configuration.
public class MyConfiguration extends Configuration {
@JsonProperty
@NotNull
@Valid
private List<WidgetFactory> widgets;
}
widgets:
- type: hammer
weight: 20
- type: chisel
radius: 0.4
You already read through the whole Dropwizard documentation? Congrats! Then you are ready to have a look into some nitty-gritty details of Dropwizard.
Below you find the startup sequence of a Dropwizard Application:
Running bundles happens in FIFO order (ConfiguredBundles are always run after Bundles).
If you have a component of your app that needs to know when Jetty is going to start, you can implement Managed as described in the dropwizard docs.
If you have a component that needs to be signaled that Jetty has started (this happens after all Managed objects’ start() methods are called), you can register with the env’s lifecycle like:
env.lifecycle().addServerLifecycleListener(new ServerLifecycleListener() {
@Override
public void serverStarted(Server server) {
/// ... do things here ....
}
});
Dropwizard wouldn’t exist without the hard work contributed by numerous individuals.
Many, many thanks to:
Dropwizard is generously supported by some companies with licenses and free accounts for their products.
JetBrains supports our open source project by sponsoring some All Products Packs within their Free Open Source License program.
ConfigurationSourceProvider
for reading resources from classpath #1314@UnwrapValidatedValue
annotation to BaseReporterFactory.frequency #1308, #1309DataSourceFactory
by deprecating PooledDataSourceFactory#getHealthCheckValidationQuery()
and PooledDataSourceFactory#getHealthCheckValidationTimeout()
#1321, #1322null
values in JAX-RS resource method parameters of type Optional<T>
as absent value after conversion #1323CachingAuthenticator
with caching results of failed authentication attempts #1082ServerFactory
#785FileAppenderFactory
. #1000FileAppenderFactory
#1218MetricRegistry
during bootstrap (e.g. with HdrHistogram) #1015ObjectMapper
during bootstrap. #1112toString
#1104Configuration
via test rules #1131SessionFactory
#1182null
values in JAX-RS resource method parameters of type Optional<T>
as absent value after conversion #1323@UnitOfWork
(#850, #915)dropwizard-spdy
from NPN to ALPNdropwizard-spdy
Task
, using metrics annotations.@PATCH
annotation for Jersey resource methods to indicate use of the HTTP PATCH
method.HttpClientBuilder
and JerseyClientBuilder
.DropwizardAppTestRule
.ScanningHibernateBundle
, which scans packages for entities, instead of requiring you to add them individually.CachingAuthenticator
that match a specified Predicate
.--catalog
and --schema
options to Liquibase.stackTracePrefix
configuration option to SyslogAppenderFactory
to configure the pattern prepended to each line in the stack-trace sent to syslog. Defaults to the TAB character, “t”. Note: this is different from the bang prepended to text logs (such as “console”, and “file”), as syslog has different conventions for multi-line messages.Optional
values using validation annotations. Such values require the @UnwrapValidatedValue
annotation, in addition to the validations you wish to use.User-Agent
for HttpClient
. Configurable via the userAgent
configuration option.AllowedMethodsFilter
. Configure allowed HTTP methods for both the application and admin connectors with allowedMethods
.CredentialProvider
for HTTP clients.SyslogAppenderFactory
failing when the application name contains a PCRE reserved character (e.g. /
or $
).DiscoverableSubtypeResolver
using the system ClassLoader
, instead of the local one.--dump
to fail to dump the database.DropwizardAppTestRule
so that it no longer requires a configuration path to operate. When no path is specified, it will now use the applications’ default configuration.Bootstrap
so that getMetricsFactory()
may now be overridden to provide a custom instance to the framework to use.io.dropwizard
group ID and namespace.dropwizard-configuration
,
dropwizard-jackson
, dropwizard-jersey
, dropwizard-jetty
, dropwizard-lifecycle
,
dropwizard-logging
, dropwizard-servlets
, dropwizard-util
, dropwizard-validation
.Environment
to separate classes: JerseyEnvironment
,
LifecycleEnvironment
, etc.dropwizard-views-freemarker
and dropwizard-views-mustache
.
dropwizard-views
just provides infrastructure now.Service
to Application
.dropwizard-forms
, which provides support for multipart MIME entities.dropwizard-spdy
.AppenderFactory
, allowing for arbitrary logging appenders for application and request
logs.ConnectorFactory
, allowing for arbitrary Jetty connectors.ServerFactory
, with multi- and single-connector implementations.ReporterFactory
, for metrics reporters, with Graphite and Ganglia implementations.ConfigurationSourceProvider
to allow loading configuration files from sources other than
the filesystem.ServerFactory
. To bind to privileged ports (e.g. 80), enable startsAsRoot
and set user
and group
, then start your application as the root user.check
command, which loads and validates the service configuration.dropwizard-client
.deflate
-encoded requests and responses.@Session HttpSession session
to have the session context injected.@Session Flash message
to have any existing flash message injected.enums
with fuzzy matching rules (i.e., whitespace
stripping, -
/_
equivalence, case insensitivity, etc.).HibernateBundle#configure(Configuration)
for customization of Hibernate configuration.DateTime
arguments and results when using JDBI.--migrations
command-line option to migrate
command to supply the migrations
file explicitly.application/json
responses.AsyncRequestLog
; now standardized on Jetty 9 NCSA format.DatabaseConfiguration
to DataSourceFactory
, and ConfigurationStrategy
to
DatabaseConfiguration
.dropwizard-db
to use tomcat-jdbc
instead of tomcat-dbcp
.ResourceTest
with ResourceTestRule
, a JUnit TestRule
.ManagedSessionFactory
.ObjectMapperFactory
; use ObjectMapper
instead.Validator
; use javax.validation.Validator
instead.dropwizard-migrations
.TaskServlet
.retries
to HttpClientConfiguration
.ServerLifecycleListener
instances.dropwizard-hibernate
.dropwizard-migrations
.http.acceptorThreadCount
to http.acceptorThreads
.ssl.keyStorePath
to ssl.keyStore
.JerseyClient
. Use Jersey’s Client
class instead.dropwizard-jdbi
.Database
. Use JDBI’s DBI
class instead.Json
class. Use ObjectMapperFactory
and ObjectMapper
instead.Validator
.Optional
resource method parameters.ResourceTest
.dropwizard-testing
.Environment
into Bootstrap
and Environment
, and broke configuration of each into
Service
’s #initialize(Bootstrap)
and #run(Configuration, Environment)
.AbstractService
and Service
.ScalaService
, so be sure to add ScalaBundle
.JerseyClientFactory
without an Environment
.Optional
support for JDBI.AsyncRequestLog
.UUIDParam
.Note
Upgrading to 0.6.0 will require changing your code. First, your Service
subclass will
need to implement both #initialize(Bootstrap<T>)
and
#run(T, Environment)
. What used to be in initialize
should be moved to run
.
Second, your representation classes need to be migrated to Jackson 2. For the most part,
this is just changing imports to com.fasterxml.jackson.annotation.*
, but there are
some subtler changes in functionality.
Finally, references to 0.5.x’s Json
, JerseyClient
, or JDBI
classes should be
changed to Jackon’s ObjectMapper
, Jersey’s Client
, and JDBI’s DBI
respectively.
dropwizard-testing
dependency.*.mustache
) to dropwizard-views
.DnsResolver
implementations in HttpClientFactory
.JerseyClientConfiguration#compressRequestEntity
for disabling the compression of request
entities.Environment#scanPackagesForResourcesAndProviders
for automatically detecting Jersey
providers and resources.Environment#setSessionHandler
.AssetServlet
:Last-Modified-At
timestamps.JacksonMessageBodyProvider
:@JsonIgnoreType
.@MinSize
, @MaxSize
, and @SizeRange
validations.@MinDuration
, @MaxDuration
, and @DurationRange
validations.TaskServlet
problems with custom context paths.jersey-text-framework-core
as an explicit dependency of dropwizard-testing
. This
helps out some non-Maven build frameworks with bugs in dependency processing.addProvider
to JerseyClientFactory
.NullPointerException
problems with anonymous health check classes.ByteBuffer
instances as JSON.supportedProtocols
to SSL configuration, and disabled SSLv2 by default.Optional<Integer>
query parameters and others.jersey-freemarker
dependency from dropwizard-views
.server
command optional.Log.forThisClass()
.@JsonIgnoreType
to JacksonMessageBodyProvider
.JsonProcessingExceptionMapper
. Now returns human-readable error messages for malformed
or invalid JSON as a 400 Bad Request
. Also handles problems with JSON generation and object
mapping in a developer-friendly way.ConfiguredCommand
.logging.console.format
, logging.file.format
, and logging.syslog.format
parameters for custom log formats.ResourceTest
to allow for enabling/disabling specific Jersey features.Configuration
serializable as JSON.Command
.java.util.logging
level changes.AssetServlet
.withBundle
to ScalaService
to enable bundle mix-ins.Optional
.AssetBundle
.WebApplicationException``s being thrown by ``JerseyClient
.Log#fatal
methods.logging.file.filenamePattern
with logging.file.currentLogFilename
and
logging.file.archivedLogFilenamePattern
.logging.file.retainedFileCount
with logging.file.archivedFileCount
.http.requestLog
now has console
, file
, and syslog
sections.ResourceTest#addProvider(Class<?>)
.ETag
and Last-Modified
support to AssetServlet
.off
logging levels conflicting with YAML’s helpfulness.Optional
support for some JDBC drivers.ResourceTest#getJson()
.CacheBuilderSpec
instances from JSON/YAML.AssetsBundle
and servlet to using cache builder specs.CachingAuthenticator
to using cache builder specs.400 Bad Request
instead of a
500 Server Error
response.connectionTimeout
, maxConnectionsPerRoute
, and keepAlive
to
HttpClientConfiguration
.HostAndPort
in configuration properties.Log
.dropwizard-templates
and added dropwizard-views
instead.AbstractParam#getMediaType()
.NullPointerException
when getting logging levels via JMX.@BearerToken
and added dropwizard-auth
instead.@CacheControl
for resource methods.AbstractService#getJson()
for full Jackson customization.ThreadNameFilter
is now added by default. The thread names Jetty worker threads are set to the
method and URI of the HTTP request they are currently processing.-Ddw.http.port=8090
will override the configuration file to set http.port
to 8090
.ManagedCommand
. It was rarely used and confusing.http.adminPort
is the same as http.port
, the admin servlet will be hosted under
/admin
. This allows Dropwizard applications to be deployed to environments like Heroku, which
require applications to open a single port.http.adminUsername
and http.adminPassword
to allow for Basic HTTP Authentication
for the admin servlet.logging.console.timeZone
and logging.file.timeZone
to control the time zone of
the timestamps in the logs. Defaults to UTC.jackson-datatype-guava
for JSON serialization/deserialization of Guava
types.InstrumentedQueuedThreadPool
from metrics-jetty
.Service
vs. ScalaService
.Configuration
subclass and its fields. Configuration files which don’t end in .yaml
or
.yml
are treated as JSON.Json
to no longer be a singleton.JsonHelpers
in dropwizard-testing
to use normalized JSON strings to compare
JSON.DatabaseConfiguration
. It’s no longer a map of connection names to configuration
objects.Database
to use the validation query in DatabaseConfiguration
for its #ping()
method.HttpConfiguration
defaults to match Jetty’s defaults.Log#setLevel(Level)
.Service#getJerseyContainer
, which allows services to fully customize the Jersey
container instance.http.contextParameters
configuration parameter.ServerCommand
. For the last time.metrics-jetty
. This allows for much
lower-level metrics about your service, including whether or not your thread pools are overloaded.ResourceTest
to dropwizard-testing
, which uses the Jersey Test Framework to provide
full testing of resources.AssetServlet
and AssetsBundle
.rootPath
to Configuration
. It allows you to serve Jersey assets off a specific path
(e.g., /resources/*
vs /*
).AssetServlet
now looks for index.htm
when handling requests for the root URI.@Timed
, @Metered
, or @ExceptionMetered
are
now instrumented via metrics-jersey
.ServerCommand
.ServerCommand#run()
non-final
.ManagedCommand
to provide access to the Environment
, among other things.JerseyClient
’s thread pool managed.Duration
and Size
configuration parameters.ConfiguredCommand
.Log
, a simple front-end for logging.No known issues exist