docs/source/manual/core.rst
.. _man-core:
############### Dropwizard Core ###############
.. highlight:: text
.. rubric:: 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.
.. _man-core-organization:
If you plan on developing a client library for other developers to access your service, we recommend
you separate your projects into three Maven modules: project-api, project-client, and
project-application.
project-api should contain your :ref:man-core-representations; project-client should use
those classes and an :ref:HTTP client <man-client> to implement a full-fledged client for your
application, and project-application should provide the actual application implementation, including
:ref:man-core-resources.
To give a concrete example of this project structure, let's say we wanted to create a Stripe_-like
API where clients can issue charges and the server would echo the charge back to the client.
stripe-api project would hold our Charge object as both the server and client want to work
with the charge and to promote code reuse, Charge objects are stored in a shared module.
stripe-app is the Dropwizard application. stripe-client abstracts away the raw HTTP
interactions and deserialization logic. Instead of using a HTTP client, users of stripe-client
would just pass in a Charge object to a function and behind the scenes, stripe-client will
call the HTTP endpoint. The client library may also take care of connection pooling, and may
provide a more friendly way of interpreting error messages. Basically, distributing a client library
for your app will help other developers integrate more quickly with the service.
If you are not planning on distributing a client library for developers, one
can combine project-api and project-application into a single project,
which tends to look like this:
com.example.myapplication:
api: :ref:man-core-representations. Request and response bodies.cli: :ref:man-core-commandsclient: :ref:Client <man-client> code that accesses external HTTP services.core: Domain implementation; where objects not used in the API such as POJOs, validations, crypto, etc, reside.db: :ref:Database <man-jdbi3> access classeshealth: :ref:man-core-healthchecksresources: :ref:man-core-resourcesMyApplication: The :ref:application <man-core-application> classMyApplicationConfiguration: :ref:configuration <man-core-configuration> class.. _Stripe: https://stripe.com/docs/api/java
.. _man-core-application:
The main entry point into a Dropwizard application is, unsurprisingly, the Application class. Each
Application has a name, which is mostly used to render the command-line interface. In the
constructor of your Application you can add :ref:man-core-bundles and :ref:man-core-commands to
your application.
.. _man-core-configuration:
Dropwizard provides a number of built-in configuration parameters. They are
well documented in the example project's configuration__ and :ref:configuration reference <man-configuration>.
.. __: https://github.com/dropwizard/dropwizard/blob/master/dropwizard-example/example.yml
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 :ref:man-core-commands-configured 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:
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/mq/MessageQueueFactory.java :language: java :start-after: // core: MessageQueueFactory :end-before: // core: MessageQueueFactory
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:
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/config/ExampleConfiguration.java :language: java :start-after: // core: ExampleConfiguration :end-before: // core: ExampleConfiguration
And your Application subclass can then use your factory to directly construct a client for the
message queue:
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/ExampleApp.java :language: java :start-after: // core: ExampleApp#run :end-before: // core: ExampleApp#run :dedent: 4
Then, in your application's YAML file, you can use a nested messageQueue field:
.. dropwizard_literalinclude:: /examples/core/src/config/config.yml :language: yaml :start-after: # core: config->messageQueue :end-before: # core: config->messageQueue
The @NotNull, @NotEmpty, @Min, @Max, and @Valid annotations are part of
:ref:man-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:: :name: config-override
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``
If you need to use the '.' character in one of the values, you can escape it by using '\\.' instead.
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.
.. _man-core-environment-variables:
The dropwizard-configuration module also provides the capabilities to substitute configuration settings with the
value of environment variables using a SubstitutingSourceProvider and EnvironmentVariableSubstitutor.
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/ExampleApp.java :language: java :start-after: // core: ExampleApp#initialize :end-before: // core: ExampleApp#initialize :dedent: 4
The configuration settings which should be substituted need to be explicitly written in the configuration file and follow the substitution rules of StringSubstitutor_ from the Apache Commons Text library.
.. dropwizard_literalinclude:: /examples/core/src/config/config.yml :language: yaml :start-after: # core: config->mySetting-defaultSetting :end-before: # core: config->mySetting-defaultSetting
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 StringSubstitutor implementation.
.. _StringSubstitutor: http://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StringSubstitutor.html
.. _man-core-ssl:
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, and Jetty's documentation_ can get you started). There is a
test keystore you can use in the Dropwizard example project__.
.. _Jetty's documentation: http://www.eclipse.org/jetty/documentation/current/configuring-ssl.html
.. __: https://github.com/dropwizard/dropwizard/tree/master/dropwizard-example
.. dropwizard_literalinclude:: /examples/core/src/config/config-https.yml :language: yaml
By default, only secure TLSv1.2 cipher suites are allowed. Older versions of cURL, Java 6 and 7, and other clients may be unable to communicate with the allowed cipher suites, but this was a conscious decision that sacrifices interoperability for security.
Dropwizard allows a workaround by specifying a customized list of cipher suites. If no lists of supported protocols or cipher suites are specified, then the JVM defaults are used. If no lists of excluded protocols or cipher suites are specified, then the defaults are inherited from Jetty.
The following list of excluded cipher suites will allow for TLSv1 and TLSv1.1 clients to negotiate a connection similar to pre-Dropwizard 1.0.
.. dropwizard_literalinclude:: /examples/core/src/config/config-https-ciphers.yml :language: yaml
.. _man-core-bootstrapping:
Since the version 9.4.8 (Dropwizard 1.2.3) Jetty supports native SSL via Google's Conscrypt_ which uses BoringSSL_
(Google's fork of OpenSSL) for handling cryptography. You can enable it in Dropwizard by registering the provider
in your app:
.. dropwizard_literalinclude:: /examples/conscrypt/pom.xml :language: xml :start-after: <!-- conscrypt: conscrypt-openjdk-uber --> :end-before: <!-- conscrypt: conscrypt-openjdk-uber --> :dedent: 8
.. dropwizard_literalinclude:: /examples/conscrypt/src/main/java/io/dropwizard/documentation/ConscryptApp.java :language: java :start-after: // conscrypt: ConscryptApp->OpenSSLProvider :end-before: // conscrypt: ConscryptApp->OpenSSLProvider :dedent: 4
and setting the JCE provider in the configuration:
.. dropwizard_literalinclude:: /examples/conscrypt/src/config/config.yaml :language: yaml
For HTTP/2 servers you need to add an ALPN Conscrypt provider as a dependency.
.. dropwizard_literalinclude:: /examples/conscrypt/pom.xml :language: xml :start-after: <!-- conscrypt: jetty-alpn-conscrypt-server --> :end-before: <!-- conscrypt: jetty-alpn-conscrypt-server --> :dedent: 8
.. note::
TLSv1.3 protocol is supported with Conscrypt and is enabled by default in Java 17+.
.. _Conscrypt: https://github.com/google/conscrypt
.. _BoringSSL: https://github.com/google/boringssl
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 :ref:man-core-bundles,
:ref:man-core-commands, or register Jackson modules to allow you to include custom types as part
of your configuration class.
.. _man-core-environments:
A Dropwizard Environment consists of all the :ref:man-core-resources, servlets, filters,
:ref:man-core-healthchecks, :ref:man-core-health, Jersey providers, :ref:man-core-managed, :ref:man-core-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:
.. code-block:: java
@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.
.. _man-core-healthchecks:
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:
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/db/DatabaseHealthCheck.java :language: java :start-after: // core: DatabaseHealthCheck :end-before: // core: DatabaseHealthCheck
You can then add this health check to your application's environment:
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/HealthCheckApp.java :language: java :start-after: // core: HealthCheckApp#run->DatabaseHealthCheck :end-before: // core: HealthCheckApp#run->DatabaseHealthCheck :dedent: 8
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).
.. note::
This behavior overlaps in many ways with the new :ref:`man-core-health` functionality. If you wish to disable
the admin health servlet, a new flag was introduced into the health check configuration
:ref:`man-configuration-healthchecks` to allow disabling it.
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.
.. _man-core-health:
The health checks described in :ref:man-core-healthchecks can be configured to create a holistic view of your
service health, which can then be used to drive decision making by things like Kubernetes readiness & liveness checks_,
or to dictate whether or not a load balancer should forward traffic to your service.
This can be done by running these dependency health checks periodically in the background on some schedule, and then aggregating the results of all of those checks into a single indicator of overall health. Certain dependencies may be critical to your application functioning, like a database that your service can't function without, but other dependencies may be more non-critical to your service being able to function (let's say a cache, that could be considered more of a nice to have than a necessity).
Define the following health check configurations in your config.yml file:
.. code-block:: yaml
health:
delayedShutdownHandlerEnabled: true
shutdownWaitPeriod: 10s
healthChecks:
- name: user-database
critical: true
- name: user-notifications-queue
critical: false
schedule:
checkInterval: 2500ms
downtimeInterval: 10s
failureAttempts: 2
successAttempts: 1
- name: user-cache
critical: false
.. note::
This behavior was integrated from the `Dropwizard Health module`_. If you are migrating from that module
to the new Dropwizard core framework health code, you will want to refer to :ref:`upgrade-notes-dropwizard-2_1_x-health` the migration guide.
.. _Kubernetes readiness & liveness checks: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
.. _Dropwizard Health module: https://github.com/dropwizard/dropwizard-health
.. _man-core-health-status:
There are two types of status that are supported currently: Alive and Ready.
alive status indicates the application is operating normally and does not need to be restarted to recover from
a stuck state. Long-running applications can eventually reach a broken state and cannot recover except by being
restarted (e.g. deadlocked threads).ready status indicates the application is ready to serve traffic. Applications can temporarily be unable to
serve traffic due to a variety of reasons, for example, an application might need to build/compute large caches
during startup or can critically depend on an external service.An example of how you might query the health check, assuming you're using the default responder/responseProvider settings in configuration:
https://<hostname>:<port>/health-check?type=<type>&name=<name>
<type> with ready or alive; defaults to ready if the type parameter is not provided<name> with the name of the health check to query. Multiple names can be provided, or no no names. If all checks are desired,
name=all can be specified to retrieve all checks.. _man-core-health-providedchecks:
Should your service have any dependencies that it needs to perform health checks against that expose either an HTTP or TCP health check interface,
you can use the HttpHealthCheck or TcpHealthCheck classes to do so easily.
You will need to register your health check(s) in your Application class run() method.
HTTP
.. code-block:: java
@Override
public void run(final AppConfiguration configuration, final Environment environment) {
...
environment.healthChecks().register("some-http-dependency", new HttpHealthCheck("http://some-http-dependency.com:8080/health-check"));
}
TCP
.. code-block:: java
@Override
public void run(final AppConfiguration configuration, final Environment environment) {
...
environment.healthChecks().register("some-tcp-dependency", new TcpHealthCheck("some-tcp-dependency.com", 443));
}
.. _man-core-health-data:
In the Application.run() method, you can access views of the health state data in two different ways:
Accessing data directly
.. code-block:: java
@Override
public void run(final AppConfiguration configuration, final Environment environment) {
...
Collection<HealthStateView> views = environment.health().healthStateAggregator().healthStateViews();
}
Listening to data changes
.. code-block:: java
@Override
public void run(final AppConfiguration configuration, final Environment environment) {
...
HealthStateListener myListener = new HealthStateListener() {
@Override
public void onStateChanged(String healthCheckName, boolean healthy) {
System.out.println(healthCheckName + "changed state to " + healthy);
}
@Override
public void onHealthyCheck(String healthCheckName) {
System.out.println(healthCheckName + "is healthy! :)");
}
@Override
public void onUnhealthyCheck(String healthCheckName) {
System.out.println(healthCheckName + "is unhealthy! :(");
}
};
environment.health().addHealthStateListener(myListener);
}
.. _man-core-managed:
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/or #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:
.. __: http://basho.com/products/
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/riak/RiakClientManager.java :language: java :start-after: // core: RiakClientManager :end-before: // core: RiakClientManager
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/ManagedApp.java :language: java :start-after: // core: ManagedApp :end-before: // core: ManagedApp
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. These managed instances use InstrumentedThreadFactory
that monitors the number of threads created, running and terminated
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/ManagedExecutorApp.java :language: java :start-after: // core: ManagedExecutorApp#run :end-before: // core: ManagedExecutorApp#run :dedent: 4
.. _man-core-bundles:
A Dropwizard bundle is a reusable group of functionality, used to define blocks of an application's
behavior by implementing the ConfiguredBundle interface.
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.
Given the bundle MyConfiguredBundle and the interface MyConfiguredBundleConfig below,
your application's Configuration subclass would need to implement MyConfiguredBundleConfig.
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/bundle/MyConfiguredBundle.java :language: java :start-after: // core: MyConfiguredBundle :end-before: // core: MyConfiguredBundle
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/bundle/MyConfiguredBundleConfig.java :language: java :start-after: // core: MyConfiguredBundleConfig :end-before: // core: MyConfiguredBundleConfig
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.
.. code-block:: yaml
server:
rootPath: /api/
.. note::
If you use the :ref:`man-configuration-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.
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/AssetsApp.java :language: java :start-after: // core: AssetsApp#initialize->AssetsBundle :end-before: // core: AssetsApp#initialize->AssetsBundle :dedent: 8
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.
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/AssetsApp.java :language: java :start-after: // core: AssetsApp#initialize->AssetsBundle->subfolders :end-before: // core: AssetsApp#initialize->AssetsBundle->subfolders :dedent: 8
.. _man-core-bundles-ssl-reload:
By registering the SslReloadBundle your application can have new certificate information
reloaded at runtime, so a restart is not necessary.
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/SslReloadApp.java :language: java :start-after: // core: SslReloadApp#initialize :end-before: // core: SslReloadApp#initialize :dedent: 4
To trigger a reload send a POST request to ssl-reload
.. code-block:: shell
curl -k -X POST 'https://localhost:<admin-port>/tasks/ssl-reload'
Dropwizard will use the same exact https configuration (keystore location, password, etc) when performing the reload.
.. note::
If anything is wrong with the new certificate (eg. wrong password in keystore), no new
certificates are loaded. So if the application and admin ports use different certificates and
one of them is invalid, then none of them are reloaded.
A http 500 error is returned on reload failure, so make sure to trap for this error with
whatever tool is used to trigger a certificate reload, and alert the appropriate admin. If the
situation is not remedied, next time the app is stopped, it will be unable to start!
.. _man-core-commands:
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.
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/MyCommand.java :language: java :start-after: // core: MyCommand :end-before: // core: MyCommand
Dropwizard recognizes our command once we add it in the initialize stage of our application.
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/CustomCommandApp.java :language: java :start-after: // core: CustomCommandApp :end-before: // core: CustomCommandApp
To invoke the new functionality, run the following:
.. code-block:: text
java -jar <jarfile> hello dropwizard
.. _man-core-commands-configured:
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.
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/MyConfiguredCommand.java :language: java :start-after: // core: MyConfiguredCommand :end-before: // core: MyConfiguredCommand
For more advanced customization of the command line (for example, having the configuration file
location specified by -c), adapt the ConfiguredCommand_ class as needed.
.. _ConfiguredCommand: https://github.com/dropwizard/dropwizard/blob/release/3.0.x/dropwizard-core/src/main/java/io/dropwizard/core/cli/ConfiguredCommand.java
.. note::
If you override the ``configure`` method, you **must** call ``super.override(subparser)`` (or call ``addFileArgument``)
in order to preserve the configuration file parameter in the subparser.
.. _man-core-tasks:
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:
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/TruncateDatabaseTask.java :language: java :start-after: // core: TruncateDatabaseTask :end-before: // core: TruncateDatabaseTask
You can then add this task to your application's environment:
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/CustomTaskApp.java :language: java :start-after: // core: CustomTaskApp#run :end-before: // core: CustomTaskApp#run :dedent: 4
Running a task can be done by sending a POST request to /tasks/{task-name} on the admin
port. The task will receive any query parameters as arguments. For example::
$ curl -X POST http://dw.example.com:8081/tasks/gc
Running GC...
Done!
You can also extend PostBodyTask to create a task which uses the body of the post request. Here's an example:
.. dropwizard_literalinclude:: /examples/core/src/main/java/io/dropwizard/documentation/EchoTask.java :language: java :start-after: // core: EchoTask :end-before: // core: EchoTask
.. _man-core-logging:
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.
.. _Logback: http://logback.qos.ch/ .. _slf4j: http://www.slf4j.org/
slf4j provides the following logging levels:
ERROR
Error events that might still allow the application to continue running.
WARN
Potentially harmful situations.
INFO
Informational messages that highlight the progress of the application at coarse-grained level.
DEBUG
Fine-grained informational events that are most useful to debug an application.
TRACE
Finer-grained informational events than the DEBUG level.
.. note::
If you don't want to use Logback, you can exclude it from Dropwizard and use an alternative logging configuration:
* Exclude Logback from the `dropwizard-core` artifact
.. code-block:: xml
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>{$dropwizard.version}</version>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
* Mark the logging configuration as external in your Dropwizard config
.. code-block:: yaml
server:
type: simple
applicationContextPath: /application
adminContextPath: /admin
requestLog:
type: external
logging:
type: external
* Disable bootstrapping Logback in your application
.. code-block:: java
public class ExampleApplication extends Application<ExampleConfiguration> {
@Override
protected void bootstrapLogging() {
}
}
.. _man-core-logging-format:
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.
To apply the prefixing with the ! symbol Dropwizard introduces several new Logback conversion words <https://logback.qos.ch/manual/layouts.html#conversionWord>_.
These are dwEx, dwException, dwThrowable, dwXEx, dwXException, dwXThrowable, dwREx and dwRootException.
These conversion words work like the ones from Logback, except that the first tab of a stack trace is replaced by a !.
The default Dropwizard logging layout uses the Dropwizard specific conversion words.
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.
.. code-block:: yaml
# 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
.. _man-core-logging-asynchronous-logging:
By default, all logging in Dropwizard is asynchronous, even to typically
synchronous sinks such as files and the console. When a slow logger (like file
logger on an overloaded disk) is coupled with a high load, Dropwizard will
seamlessly drop events of lower importance (TRACE, DEBUG, INFO) in
an attempt to maintain reasonable latency.
.. TIP::
Instead of logging business critical statements under INFO, insert them
into a database, durable message queue, or another mechanism that gives
confidence that the request has satisfied business requirements before
returning the response to the client.
This logging behavior :ref:can be configured <man-configuration-logging>:
discardingThreshold to 0 so that no events are droppedneverBlock to true so that even WARN and ERROR levels will be discarded from logging under heavy loadRequest access logging has the same logging behavior, and since all request
logging is done under INFO, each log statement has an equal chance of being
dropped if the log queue is nearing full.
.. _man-core-logging-console:
By default, Dropwizard applications log INFO and higher to STDOUT. You can configure this by
editing the logging section of your YAML configuration file:
.. code-block:: yaml
logging:
appenders:
- type: console
threshold: WARN
target: stderr
In the above, we're instead logging only WARN and ERROR messages to the STDERR device.
.. _man-core-logging-file:
Dropwizard can also log to an automatically rotated set of log files. This is the recommended configuration for your production environment:
.. code-block:: yaml
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
.. _man-core-logging-syslog:
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.
.. code-block:: yaml
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:
.. code-block:: yaml
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
.. _man-core-logging-http-config:
You may prefer to produce logs in a structured format such as JSON, so it can be processed by analytics or BI software. For that, add a module to the project for supporting JSON layouts:
.. code-block:: xml
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-json-logging</artifactId>
<version>${dropwizard.version}</version>
</dependency>
Setup the JSON layout in the configuration file.
For general logging:
.. code-block:: yaml
logging:
appenders:
- type: console
layout:
type: json
The json layout will produces the following log message:
.. code-block:: json
{"timestamp":1515002688000, "level":"INFO","logger":"org.eclipse.jetty.server.Server","thread":"main","message":"Started @6505ms"}
For request logging:
.. code-block:: yaml
server:
requestLog:
appenders:
- type: console
layout:
type: access-json
The access-json layout will produces the following log message:
.. code-block:: json
{"timestamp":1515002688000, "method":"GET","uri":"/hello-world", "status":200, "protocol":"HTTP/1.1","contentLength":37,"remoteAddress":"127.0.0.1","requestTime":5, "userAgent":"Mozilla/5.0"}
Active log levels can be changed during the runtime of a Dropwizard application via HTTP using
the LogConfigurationTask. For instance, to configure the log level for a
single Logger. The logger parameter may be repeated. The optional duration parameter must be an ISO 8601 duration format.
When the duration elapses the level will revert to the effective level of the parent logger.:
.. code-block:: shell
# Configure com.example.helloworld to INFO
curl -X POST -d "logger=com.example.helloworld&level=INFO" http://localhost:8081/tasks/log-level
# Configure com.example.helloworld and com.example.helloearth to INFO
curl -X POST -d "logger=com.example.helloworld&logger=com.example.helloearth&level=INFO" http://localhost:8081/tasks/log-level
# Configure com.example.helloworld to INFO, then revert to default level after 10 minutes
curl -X POST -d "logger=com.example.helloworld&level=INFO&duration=PT10M" http://localhost:8081/tasks/log-level
# Revert com.example.helloworld to the default level
curl -X POST -d "logger=com.example.helloworld" http://localhost:8081/tasks/log-level
.. note::
Chaining log level changes on the same package may have unexpected consequences due to the naive implementation of a
simple FIFO timer.
.. _man-core-logging-filters:
Just because a statement has a level of INFO, doesn't mean it should be logged with other INFO statements. One can create logging filters that will intercept log statements before they are written and decide if they're allowed. Log filters can work on both regular statements and request log statements. The following example will be for request logging as there are many reasons why certain requests may be excluded from the log:
The example will demonstrate excluding /secret requests from the log.
.. code-block:: java
@JsonTypeName("secret-filter-factory")
public class SecretFilterFactory implements FilterFactory<IAccessEvent> {
@Override
public Filter<IAccessEvent> build() {
return new Filter<IAccessEvent>() {
@Override
public FilterReply decide(IAccessEvent event) {
if (event.getRequestURI().equals("/secret")) {
return FilterReply.DENY;
} else {
return FilterReply.NEUTRAL;
}
}
};
}
}
Reference SecretFilterFactory type in our configuration.
.. code-block:: yaml
server:
requestLog:
appenders:
- type: console
filterFactories:
- type: secret-filter-factory
The last step is to add our class (in this case com.example.SecretFilterFactory) to META-INF/services/io.dropwizard.logging.common.filter.FilterFactory in our resources folder.
.. _man-core-request-log-url-filtering:
Reference UriFilterFactory type in your configuration.
.. code-block:: yaml
server:
requestLog:
appenders:
- type: console
filterFactories:
- type: uri
uris:
- "/health-check"
.. _man-core-testing-applications:
All of Dropwizard's APIs are designed with testability in mind, so even your applications can have unit tests:
.. dropwizard_literalinclude:: /examples/core/src/test/java/io/dropwizard/documentation/MyApplicationTest.java :language: java :start-after: // core: MyApplicationTest :end-before: // core: MyApplicationTest
We highly recommend Mockito_ for all your mocking needs.
.. _Mockito: https://site.mockito.org/
.. _man-core-banners:
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.core.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.
.. _TAAG: http://patorjk.com/software/taag/
.. _man-core-resources:
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.
.. __: https://jersey.github.io/
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:
.. _man-core-resources-example:
.. code-block:: java
@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") OptionalLong userId,
@QueryParam("count") @DefaultValue("20") OptionalInt 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") OptionalLong userId,
@NotNull @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.
.. _man-core-resources-paths:
.. 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.
.. __: http://tools.ietf.org/html/draft-gregorio-uritemplate-07
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.
.. _man-core-resources-methods:
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 :ref:list of allowed methods <man-configuration-all>. 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
:ref:negotiated media type <man-core-resources-media-types>. In this case, our resource only supports
JSON, and so the NotificationList is serialized to JSON using Jackson.
.. _man-core-resources-metrics:
Every resource method or the class itself can be annotated with @Timed, @Metered, @ResponseMetered and @ExceptionMetered. If the annotation is placed on the class, it will apply to all its resource methods. Dropwizard augments Jersey to automatically record runtime information about your resource methods.
.. code-block:: java
public class ExampleApplication extends ResourceConfig {
.
.
.
register(new InstrumentedResourceMethodApplicationListener (new MetricRegistry()));
config = config.register(ExampleResource.class);
.
.
.
}
@Path("/example")
@Produces(MediaType.TEXT_PLAIN)
public class ExampleResource {
@GET
@Timed
public String show() {
return "yay";
}
@GET
@Metered(name = "fancyName") // If name isn't specified, the meter will given the name of the method it decorates.
@Path("/metered")
public String metered() {
return "woo";
}
@GET
@ExceptionMetered(cause = IOException.class) // Default cause is Exception.class
@Path("/exception-metered")
public String exceptionMetered(@QueryParam("splode") @DefaultValue("false") boolean splode) throws IOException {
if (splode) {
throw new IOException("AUGH");
}
return "fuh";
}
@GET
@ResponseMetered
@Path("/response-metered")
public Response responseMetered(@QueryParam("invalid") @DefaultValue("false") boolean invalid) {
if (invalid) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
return Response.ok().build();
}
}
@Timed measures the duration of requests to a resource@Metered measures the rate at which the resource is accessed@ResponseMetered measures rate for each class of response codes (1xx/2xx/3xx/4xx/5xx)@ExceptionMetered measures how often exceptions occur processing the resource.. important::
``@Timed`` and ``@Metered`` can only be used on the same resource method at the same time, if
their name is unique, also see the annotation parameter ``name``.
Otherwise, the generated metrics names will be identical which will cause an ``IllegalArgumentException``.
.. _man-core-resources-parameters:
The annotated methods on a resource class can accept parameters which are mapped to from aspects of the incoming request.
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 OptionalInt parameter takes the first count value from
the request's query string and passes it as a String to OptionalInt's constructor.
OptionalInt 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.
.. _man-core-resources-request-entities:
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
:ref:example code <man-core-resources-example>, the add method provides a good example of
this:
.. code-block:: java :emphasize-lines: 3
@POST
public Response add(@PathParam("user") OptionalLong userId,
@NotNull @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 :ref:man-validation-validations-constraining-entities.
If the deserialized Notification isn't valid, Dropwizard returns a 422 Unprocessable Entity
response to the client.
.. note::
If a request entity parameter is just annotated with ``@Valid``, it is still allowed to be
``null``, so to ensure that the object is present and validated ``@NotNull @Valid`` is a
powerful combination.
.. _man-core-resources-media-types:
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.
.. _man-core-resources-responses:
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:
.. code-block:: java
return Response.noContent().language(Locale.GERMAN).build();
In general, though, we recommend you return actual domain objects if at all possible. It makes
:ref:testing resources <man-core-resources-testing> much easier.
.. _man-core-resource-error-handling:
Almost as important as an application's happy path (receiving expected input and returning expected output) is an application's behavior when something goes wrong.
If your resource class unintentionally throws an exception, Dropwizard will log that exception under
the ERROR level (including stack traces) and return a terse, safe application/json 500 Internal Server Error response. The response will contain an ID that can be grepped out the server
logs for additional information.
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, as that will make resource endpoints more self describing and easier to test.
The least intrusive way to map error conditions to a response is to throw a WebApplicationException:
.. code-block:: java
@GET
@Path("/{collection}")
public Saying reduceCols(@PathParam("collection") String collection) {
if (!collectionMap.containsKey(collection)) {
final String msg = String.format("Collection %s does not exist", collection);
throw new WebApplicationException(msg, Status.NOT_FOUND)
}
// ...
}
In this example a GET request to /foobar will return
.. code-block:: json
{"code":404,"message":"Collection foobar does not exist"}
One can also take exceptions that your resource may throw and map them to appropriate responses. For instance,
an endpoint may throw IllegalArgumentException and it may be worthy enough of a response to warrant a
custom metric to track how often the event occurs. Here's an example of such an ExceptionMapper
.. code-block:: java
public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> {
private final Meter exceptions;
public IllegalArgumentExceptionMapper(MetricRegistry metrics) {
exceptions = metrics.meter(name(getClass(), "exceptions"));
}
@Override
public Response toResponse(IllegalArgumentException e) {
exceptions.mark();
return Response.status(Status.BAD_REQUEST)
.header("X-YOU-SILLY", "true")
.type(MediaType.APPLICATION_JSON_TYPE)
.entity(new ErrorMessage(Status.BAD_REQUEST.getStatusCode(),
"You passed an illegal argument!"))
.build();
}
}
and then registering the exception mapper:
.. code-block:: java
@Override
public void run(final MyConfiguration conf, final Environment env) {
env.jersey().register(new IllegalArgumentExceptionMapper(env.metrics()));
env.jersey().register(new Resource());
}
Overriding Default Exception Mappers
To override a specific exception mapper, register your own class that implements the same
``ExceptionMapper<T>`` as one of the default. For instance, we can customize responses caused by
Jackson exceptions:
.. code-block:: java
public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException> {
@Override
public Response toResponse(JsonProcessingException exception) {
// create the response
}
}
With this method, one doesn't need to know what the default exception mappers are, as they are
overridden if the user supplies a conflicting mapper. While not preferential, one can also disable
all default exception mappers, by setting ``server.registerDefaultExceptionMappers`` to ``false``.
See the class ``ExceptionMapperBinder`` for a list of the default exception mappers.
.. _man-core-resources-uris:
URIs
----
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:
.. code-block:: java
UriBuilder.fromResource(UserResource.class).build(user.getId());
.. _man-core-resources-testing:
Testing
-------
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_.)
.. code-block:: java
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));
}
}
Caching
-------
Adding a ``Cache-Control`` statement to your resource class is simple with Dropwizard:
.. code-block:: java
@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.
Sessions
--------
Although Dropwizard's main purpose is to build stateless RESTful APIs, a stateful web service can
be built using HTTP sessions. As most users won't profit from having session support enabled by
default, session support is implemented as opt-in.
The underlying Jetty server will handle sessions only if a ``SessionHandler`` is provided at
application startup. Therefore the following code has to be added to the ``run`` method of the
``Application`` class:
.. code-block:: java
@Override
public void run(final TestConfiguration configuration, final Environment environment) {
environment.servlets().setSessionHandler(new org.eclipse.jetty.server.session.SessionHandler());
}
This will provide Jetty's default ``SessionHandler`` to the servlet environment and session support is enabled.
To get an ``HttpSession`` object injected into a Jersey resource method, Dropwizard provides a ``@Session``
annotation:
.. code-block:: java
public Response doSomethingWithSessions(@Session HttpSession httpSession) {
return Response.ok().build();
}
.. _man-core-representations:
Representations
===============
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.
.. _man-core-representations-basic:
Basic JSON
----------
Jackson is awesome at converting regular POJOs to JSON and back. This file:
.. code-block:: java
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:
.. code-block:: javascript
{
"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:
.. code-block:: java
public class Notification {
private final String text;
@JsonCreator
public Notification(@JsonProperty("text") String text) {
this.text = text;
}
@JsonProperty("text")
public String getText() {
return text;
}
}
.. _man-core-representations-advanced:
Advanced JSON
-------------
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:
.. code-block:: java
@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>``.
.. _man-core-representations-advanced-snake-case:
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``:
.. code-block:: java
@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:
.. code-block:: javascript
{
"first_name": "Coda"
}
.. _man-core-representations-streaming:
Unknown properties
~~~~~~~~~~~~~~~~~~
If the name of a JSON property cannot be mapped to a Java property (or otherwise handled), that
JSON property will simply be ignored.
You can change this behavior by configuring Dropwizard's object mapper:
.. code-block:: java
public void initialize(Bootstrap<ExampleConfiguration> bootstrap) {
bootstrap.getObjectMapper().enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
.. note::
The YAML configuration parser will fail on unknown properties regardless of the object mapper
configuration.
Streaming Output
----------------
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.
.. _man-core-representations-html:
HTML Representations
--------------------
For generating HTML pages, check out Dropwizard's :ref:`views support <manual-views>`.
.. _man-core-representations-custom:
Custom Representations
----------------------
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.
.. _man-core-filters:
Filters
=======
There might be cases when you want to filter out requests or modify them before they reach your Resources.
.. _man-core-jersey-filters:
Jersey filters
--------------
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.
.. _filters and interceptors: https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/filters-and-interceptors.html
.. code-block:: java
@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`_:
.. _DynamicFeature: https://docs.oracle.com/javaee/7/api/javax/ws/rs/container/DynamicFeature.html
.. code-block:: java
@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:
.. code-block:: java
environment.jersey().register(DateRequiredFeature.class);
.. _man-core-servlet-filters:
Servlet filters
---------------
Another way to create filters is by creating servlet filters. They offer a way 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:
.. _bundled: http://www.eclipse.org/jetty/documentation/current/advanced-extras.html
.. code-block:: java
public class DateNotSpecifiedServletFilter implements jakarta.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 will be active. Here's an example:
.. code-block:: java
environment.servlets().addFilter("DateNotSpecifiedServletFilter", new DateNotSpecifiedServletFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
.. _man-glue-detail:
How it's glued together
=======================
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 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