Dropwizard is a Java framework for building RESTful web services that bundles stable libraries (Jetty, Jersey, Jackson, Hibernate Validator and Metrics) into one runnable JAR. We write a configuration class, an application class and JAX-RS resources, and Dropwizard starts an embedded Jetty server with an application port for the API and an admin port for health checks and metrics.
We use Dropwizard when we want a small, fast REST service with production features such as metrics, health checks and logging, without a large framework around it. The following example is the complete run() method of a hello world task service. It registers two REST resources and one health check.
@Override
public void run(TaskConfiguration config, Environment environment) {
TaskRepository repository = new TaskRepository();
environment.jersey().register(new HelloResource(config.getTemplate(), config.getDefaultName()));
environment.jersey().register(new TaskResource(repository));
environment.healthChecks().register("task-store", new TaskStoreHealthCheck(repository));
}
After java -jar starts the service, the hello endpoint returns JSON.
$ curl -s 'http://localhost:18080/hello?name=Lokesh'
{"id":2,"content":"Hello, Lokesh!"}
In this Dropwizard tutorial, we build that service step by step on Dropwizard 5.0.2 and Java 25, run it as a fat JAR, call every endpoint with curl and test it with JUnit 6. All Dropwizard articles are listed on the Dropwizard tutorials page.
1. How a Dropwizard Application Is Built
A Dropwizard service has three main parts. A Configuration subclass holds the settings, Dropwizard reads them from a YAML file and validates them with Bean Validation before the server starts. An Application subclass registers the resources, health checks and other components in run(), and JAX-RS resource classes handle the HTTP requests.

Dropwizard 5 needs Java 17 or later and uses the Jakarta namespaces, so resources import jakarta.ws.rs and not javax.ws.rs. The 5.0.2 release uses Jetty 12.1, Jersey 3.1, Jackson 2.21 and Hibernate Validator 8, which we checked with mvn dependency:tree.
2. Maven Setup With the Dropwizard BOM
The Dropwizard BOM manages the versions of all Dropwizard modules, so we import it once and declare the modules without versions. The dropwizard-core module is enough for a REST service, and dropwizard-testing adds the JUnit extensions.
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-bom</artifactId>
<version>5.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<scope>test</scope>
</dependency>
The first block goes into dependencyManagement. To run the service as one JAR, we build a fat JAR with the Maven Shade plugin. The ServicesResourceTransformer merges the META-INF/services files of all libraries, and Dropwizard needs those files to find the connector and logging types named in config.yml.
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.howtodoinjava.dropwizard.helloworld.TaskApplication</mainClass>
</transformer>
</transformers>
Without the first transformer, the JAR fails at startup with the error described in Could not resolve type id ‘http’ as a subtype of ConnectorFactory.
3. The Configuration Class and config.yml
Each field of the configuration class maps to a key in the YAML file. Jackson fills the fields through the @JsonProperty getters and setters, and the validation annotations stop the application from starting when a value is missing.
public class TaskConfiguration extends Configuration {
@NotBlank
private String template;
@NotBlank
private String defaultName = "Stranger";
@JsonProperty
public String getTemplate() {
return template;
}
The server block configures the two Jetty connectors. The application connector serves our API on port 8080, and the admin connector serves operational endpoints on port 8081.
template: "Hello, %s!"
defaultName: Stranger
server:
applicationConnectors:
- type: http
port: 8080
bindHost: 127.0.0.1
adminConnectors:
- type: http
port: 8081
bindHost: 127.0.0.1
The check command validates the file without starting the server, which is useful in a CI pipeline. When we remove the template line, the check fails with a clear message.
$ java -jar target/dropwizard-hello-world-1.0.0.jar check config.yml
INFO [2026-10-11 16:10:12,443] io.dropwizard.core.cli.CheckCommand: Configuration is OK
config.yml has an error:
* template must not be blank
4. The Application Class
The application class extends Application<TaskConfiguration>. The method initialize() adds bundles and commands before the configuration is read, and run() receives the parsed configuration and the Environment, where we register resources, health checks, filters and managed objects.
public class TaskApplication extends Application<TaskConfiguration> {
public static void main(String[] args) throws Exception {
new TaskApplication().run(args);
}
@Override
public String getName() {
return "task-service";
}
The health check reports the state of the task store. Dropwizard logs a warning at startup when an application has no health check, because monitoring tools call the admin port to check whether the instance works. The Dropwizard health check article covers checks in depth.
@Override
protected Result check() {
int count = repository.count();
return Result.healthy("%d tasks in memory", count);
}
5. REST Resources for Hello World and Tasks
A resource is a plain JAX-RS class. The hello resource takes an optional query parameter and builds the greeting from the template in config.yml. Jackson turns the returned record into JSON.
@GET
public Greeting sayHello(@QueryParam("name") Optional<String> name) {
String content = template.formatted(name.orElse(defaultName));
return new Greeting(counter.incrementAndGet(), content);
}
public record Greeting(long id, String content) {
}
A to-do app needs more than a greeting, so the task resource adds create, read, update and delete operations. The request body is validated with @Valid, so a blank title never reaches the repository.
@POST
public Response create(@NotNull @Valid NewTask newTask) {
Task task = repository.save(newTask.title());
return Response.created(URI.create("/tasks/" + task.id())).entity(task).build();
}
@PUT
@Path("/{id}/done")
public Task markDone(@PathParam("id") long id) {
return repository.markDone(id)
.orElseThrow(() -> new NotFoundException("Task " + id + " not found"));
}
public record NewTask(@NotBlank @Size(max = 80) String title) {
}
6. Build, Run and Call the Service
We build the fat JAR with mvn package and start it with the server command and the configuration file.
mvn package
java -jar target/dropwizard-hello-world-1.0.0.jar server config.yml
At startup, Dropwizard prints every registered path, which is a quick way to confirm that the resources were registered.
GET /hello (com.howtodoinjava.dropwizard.helloworld.resources.HelloResource)
GET /tasks (com.howtodoinjava.dropwizard.helloworld.resources.TaskResource)
POST /tasks (com.howtodoinjava.dropwizard.helloworld.resources.TaskResource)
DELETE /tasks/{id} (com.howtodoinjava.dropwizard.helloworld.resources.TaskResource)
GET /tasks/{id} (com.howtodoinjava.dropwizard.helloworld.resources.TaskResource)
PUT /tasks/{id}/done (com.howtodoinjava.dropwizard.helloworld.resources.TaskResource)
Our run used ports 18080 and 18081 through system properties, as section 7 shows. A POST creates a task and returns 201 with the Location header.
$ curl -s -i -X POST http://localhost:18080/tasks -H 'Content-Type: application/json' -d '{"title":"Buy milk"}'
HTTP/1.1 201 Created
Location: http://localhost:18080/tasks/1
Content-Type: application/json
{"id":1,"title":"Buy milk","done":false}
$ curl -s -X PUT http://localhost:18080/tasks/1/done
{"id":1,"title":"Buy milk","done":true}
Errors are returned as JSON too. An unknown ID gives 404, a blank title gives 422 with the validation message, and broken JSON gives 400.
{"code":404,"message":"Task 42 not found"}
HTTP/1.1 422 Unprocessable Entity
{"errors":["title must not be blank"]}
{"code":400,"message":"Unable to process JSON"}
The admin port exposes the health and liveness endpoints for operators.
$ curl -s http://localhost:18081/ping
pong
7. Change the Port and Other Settings
We change ports in the server block of config.yml. To override one value for a single run, Dropwizard reads system properties with the prefix dw., e.g. -Ddw.server.applicationConnectors[0].port=18080, which is how our test run avoided port conflicts.
java -Ddw.server.applicationConnectors[0].port=18080 -Ddw.server.adminConnectors[0].port=18081 -jar target/dropwizard-hello-world-1.0.0.jar server config.yml
8. Test the Resources With JUnit 6
The dropwizard-testing module has two JUnit extensions. ResourceExtension runs one resource in an in-memory Jersey server, which is fast and fits unit tests.
static final ResourceExtension RESOURCES = ResourceExtension.builder()
.addResource(new TaskResource(REPOSITORY))
.build();
@Test
void rejectsBlankTitle() {
Response response = RESOURCES.target("/tasks").request()
.post(Entity.json(new NewTask(" ")));
assertEquals(422, response.getStatus());
assertTrue(response.readEntity(String.class).contains("title must not be blank"));
}
DropwizardAppExtension starts the whole application with a test configuration on random ports, so we can call real HTTP endpoints, including the admin port.
static final DropwizardAppExtension<TaskConfiguration> APP = new DropwizardAppExtension<>(
TaskApplication.class, ResourceHelpers.resourceFilePath("test-config.yml"));
Both test classes need @ExtendWith(DropwizardExtensionsSupport.class). All five tests pass with mvn test on JUnit 6.1.3. For calling other services from a Dropwizard app, see the Dropwizard client article.
9. Dropwizard Tutorial FAQs
Readers who start with Dropwizard often compare it with Spring Boot and want to know the Java version and the default ports.
9.1. What Is Dropwizard Used For?
Dropwizard is used to build REST APIs and small backend services that run as one JAR. It bundles Jetty, Jersey, Jackson, validation, logging, metrics and health checks with sensible defaults, so a service is production-ready from the first build.
9.2. Dropwizard vs Spring Boot, Which One Should We Pick?
Pick Spring Boot when the team needs its large set of projects, such as Spring Data, Spring Security and many integrations. Pick Dropwizard when we want a small, explicit stack built on JAX-RS with less framework code, and we are fine wiring objects ourselves in run().
9.3. Which Java Version Does Dropwizard 5 Need?
Dropwizard 5.0 needs Java 17 or later. The example in this article runs on Java 25.
9.4. What Are the Default Ports of a Dropwizard Application?
The application connector uses port 8080 and the admin connector uses port 8081. Both change in the server block of config.yml or with dw. system properties.
10. Conclusion
A Dropwizard service is a configuration class, an application class and a few JAX-RS resources, packaged into one fat JAR. Dropwizard validates the configuration before the application starts, returns JSON errors for bad requests and gives us an admin port with health checks and metrics.
With the BOM, the Shade plugin and the two test extensions, the same project layout works for a hello world service and for a larger service. Next, protect the endpoints with the Dropwizard basic auth example.
11. References
- Dropwizard Getting Started
- Dropwizard Core Manual
- Dropwizard Testing Manual
- Maven Shade Plugin Resource Transformers
Happy Learning !!