DEV Community

Cover image for ๐Ÿ“š Complete Java Microservices Guide: Dropwizard & Ecosystem
Ajinkya Singh
Ajinkya Singh

Posted on

๐Ÿ“š Complete Java Microservices Guide: Dropwizard & Ecosystem

A Step-by-Step Beginner-to-Intermediate Learning Path


How to use this guide: Read chapter by chapter. Every concept builds on the previous one. Code examples are practical and runnable. Chapters marked ๐Ÿ”ฒ are TODO โ€” topics to study next.


๐Ÿ“‹ Table of Contents


Chapter 1: What is Dropwizard & Why Use It?

๐Ÿค” The Problem It Solves

Imagine you want to build a Java REST API. You'd need to:

  • Pick a web server (Jetty, Tomcat?)
  • Pick a JSON library (Jackson, Gson?)
  • Pick a validation library
  • Pick a database library
  • Pick a metrics/monitoring library
  • Make sure they all work together (version conflicts are a nightmare!)

This research alone can take weeks and paralyze a developer.

โœ… Dropwizard's Solution

Dropwizard is a curated collection of best-of-breed libraries glued together so they work out of the box.

Component Library Used
Web Server Jetty (embedded)
REST Framework Jersey (JAX-RS)
JSON Jackson
Validation Hibernate Validator
Database Migrations Liquibase
Metrics & Health Metrics library
Logging Logback + SLF4J
Config YAML + Jackson

๐Ÿซ™ The Fat JAR Concept

Traditional Java apps rely on an app server (Tomcat, GlassFish) being installed on the machine. The problem:

"It works on my computer" ๐Ÿ˜ค
Enter fullscreen mode Exit fullscreen mode

Dropwizard packages everything โ€” your code + all dependencies + the web server โ€” into a single JAR file.

myapp.jar  โ† Contains Jetty, Jersey, Jackson, your code... everything!
Enter fullscreen mode Exit fullscreen mode

To run it:

java -jar myapp.jar server config.yml
Enter fullscreen mode Exit fullscreen mode

That's it. No app server needed. No "works on my machine" problem.

๐Ÿ—๏ธ Microservices Context

Microservices = breaking a big monolithic app into small, independent services. Each service:

  • Does one thing well
  • Has its own database
  • Communicates via REST/HTTP
  • Can be deployed independently

Dropwizard is perfect for microservices because each service is a single JAR that starts up fast.

๐Ÿ†š Dropwizard vs Spring Boot

Feature Dropwizard Spring Boot
Philosophy Opinionated, curated Flexible, extensive
Learning curve Easier if you know JAX-RS Easier if you know Spring
Startup speed Fast Moderate
Ecosystem size Smaller Huge
Best for REST APIs, microservices Anything Java

Which to choose? If you're building a focused REST microservice, Dropwizard is excellent. For complex enterprise apps, Spring Boot has more features.


Chapter 2: Project Setup & Fat JAR

๐Ÿ› ๏ธ Prerequisites

Make sure you have installed:

  • Java 8+ (Dropwizard 1.x requires Java 8)
  • Maven 3.x
  • An IDE (IntelliJ IDEA recommended)

๐Ÿ“ Creating a Project with Maven Archetype

The fastest way to start:

mvn archetype:generate \
  -DarchetypeGroupId=io.dropwizard.archetypes \
  -DarchetypeArtifactId=java-simple \
  -DarchetypeVersion=1.3.29 \
  -DgroupId=com.example \
  -DartifactId=my-api \
  -Dversion=1.0-SNAPSHOT \
  -Dname=MyApi
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Important: The -Dname=MyApi parameter is used in generated file names. Always provide it!

๐Ÿ“‚ Generated Project Structure

my-api/
โ”œโ”€โ”€ pom.xml
โ””โ”€โ”€ src/
    โ””โ”€โ”€ main/
        โ”œโ”€โ”€ java/
        โ”‚   โ””โ”€โ”€ com/example/
        โ”‚       โ”œโ”€โ”€ MyApiApplication.java   โ† Main class (entry point)
        โ”‚       โ””โ”€โ”€ MyApiConfiguration.java โ† Config class
        โ””โ”€โ”€ resources/
            โ””โ”€โ”€ config.yml                  โ† Configuration file
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“„ The pom.xml โ€” Your Project's Heart

<project>
    <groupId>com.example</groupId>
    <artifactId>my-api</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <dropwizard.version>1.3.29</dropwizard.version>
    </properties>

    <dependencies>
        <!-- Core Dropwizard -->
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-core</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- This plugin creates the Fat JAR -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <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">
                                    <!-- This tells Java which class has main() -->
                                    <mainClass>com.example.MyApiApplication</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ The Application Class

package com.example;

import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

public class MyApiApplication extends Application<MyApiConfiguration> {

    // Java's main method โ€” this starts everything
    public static void main(String[] args) throws Exception {
        new MyApiApplication().run(args);
    }

    @Override
    public String getName() {
        return "my-api";  // Name of your app
    }

    @Override
    public void initialize(Bootstrap<MyApiConfiguration> bootstrap) {
        // Register bundles (plugins) here
    }

    @Override
    public void run(MyApiConfiguration configuration, Environment environment) {
        // Register your resources (REST endpoints) here
        // This is where the app "comes alive"
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ—๏ธ Building & Running

# Build the Fat JAR
mvn clean package

# Run with server command
java -jar target/my-api-1.0-SNAPSHOT.jar server config.yml

# Check if it's alive (admin port 8081)
curl http://localhost:8081/ping

# Your API is on port 8080 by default
curl http://localhost:8080/hello
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“‹ Built-in Commands

# Validate your config file (catches errors before running!)
java -jar my-api.jar check config.yml

# Run the server
java -jar my-api.jar server config.yml

# Database commands (when Liquibase is added)
java -jar my-api.jar db migrate config.yml
java -jar my-api.jar db status config.yml
java -jar my-api.jar db drop-all --confirm-delete-everything config.yml
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก Pro tip: Run check before server in deployment scripts to catch config errors early!


Chapter 3: Configuration with YAML

๐Ÿคท Why Configuration Matters

Hard-coding things like database URLs, passwords, and ports in code is bad practice. Configuration files let you:

  • Have different settings for dev/test/production
  • Change settings without recompiling
  • Keep secrets out of code

๐Ÿ“„ The config.yml File

# Server ports
server:
  applicationConnectors:
    - type: http
      port: 8080
  adminConnectors:
    - type: http
      port: 8081

# Logging
logging:
  level: INFO
  loggers:
    com.example: DEBUG  # More verbose logging for your package

# Custom config (your own properties)
login: admin
password: secret123

# Database (added later with Hibernate bundle)
database:
  driverClass: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/mydb
  user: dbuser
  password: dbpassword
Enter fullscreen mode Exit fullscreen mode

โ˜• The Configuration Class

Every key in config.yml maps to a field in your Configuration class:

package com.example;

import io.dropwizard.Configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

public class MyApiConfiguration extends Configuration {

    // @NotEmpty = Hibernate Validator annotation
    // If this field is empty/missing, app won't start!
    @NotEmpty
    private String login;

    @NotEmpty
    private String password;

    // @JsonProperty tells Jackson the YAML key name
    @JsonProperty
    public String getLogin() {
        return login;
    }

    @JsonProperty
    public void setLogin(String login) {
        this.login = login;
    }

    @JsonProperty
    public String getPassword() {
        return password;
    }

    @JsonProperty
    public void setPassword(String password) {
        this.password = password;
    }
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŒ Multiple Config Files (Best Practice)

# Development
java -jar my-api.jar server config-dev.yml

# Production  
java -jar my-api.jar server config-prod.yml

# Testing (uses H2 in-memory database)
java -jar my-api.jar server config-test.yml
Enter fullscreen mode Exit fullscreen mode

Each config file can point to different databases, use different ports, etc.

๐Ÿ” Accessing Config in Your App

@Override
public void run(MyApiConfiguration config, Environment environment) {
    // Access config values
    String login = config.getLogin();
    String password = config.getPassword();

    // Pass them to classes that need them
    environment.jersey().register(new MyResource(login, password));
}
Enter fullscreen mode Exit fullscreen mode

Chapter 4: Building REST Resources (JAX-RS)

๐ŸŒ What is JAX-RS?

JAX-RS is a Java standard (specification) for building REST APIs using annotations. Dropwizard uses Jersey as the JAX-RS implementation.

The key idea: annotate a regular Java class with special annotations, and Jersey turns it into a REST endpoint.

๐Ÿ“Œ Core Annotations

Annotation Meaning
@Path("/users") URL path this class/method handles
@GET Responds to HTTP GET requests
@POST Responds to HTTP POST requests
@PUT Responds to HTTP PUT requests
@DELETE Responds to HTTP DELETE requests
@Produces(MediaType.APPLICATION_JSON) Returns JSON
@Consumes(MediaType.APPLICATION_JSON) Accepts JSON input
@PathParam("id") Gets value from URL: /users/42 โ†’ id=42
@QueryParam("name") Gets value from query string: ?name=John

โ˜• Your First Resource Class

package com.example.resources;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.ArrayList;

@Path("/bookmarks")         // All methods in this class handle /bookmarks
@Produces(MediaType.APPLICATION_JSON)  // All responses are JSON
@Consumes(MediaType.APPLICATION_JSON)  // All requests accept JSON
public class BookmarkResource {

    private List<Bookmark> bookmarks = new ArrayList<>();

    // GET /bookmarks โ†’ returns all bookmarks
    @GET
    public List<Bookmark> getAllBookmarks() {
        return bookmarks;
    }

    // GET /bookmarks/42 โ†’ returns one bookmark by ID
    @GET
    @Path("/{id}")
    public Bookmark getBookmark(@PathParam("id") long id) {
        return bookmarks.stream()
            .filter(b -> b.getId() == id)
            .findFirst()
            .orElseThrow(() -> new NotFoundException("Bookmark not found: " + id));
    }

    // POST /bookmarks โ†’ create a new bookmark
    @POST
    public Response createBookmark(Bookmark bookmark) {
        bookmarks.add(bookmark);
        return Response.status(Response.Status.CREATED)
                       .entity(bookmark)
                       .build();
    }

    // DELETE /bookmarks/42 โ†’ delete a bookmark
    @DELETE
    @Path("/{id}")
    public Response deleteBookmark(@PathParam("id") long id) {
        bookmarks.removeIf(b -> b.getId() == id);
        return Response.noContent().build();  // 204 No Content
    }

    // GET /bookmarks/search?tag=java โ†’ filter by query param
    @GET
    @Path("/search")
    public List<Bookmark> searchByTag(@QueryParam("tag") String tag) {
        // filter logic here
        return bookmarks;
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ The Domain Model (Bookmark.java)

package com.example.core;

public class Bookmark {
    private long id;
    private String url;
    private String title;
    private String tag;

    // Default constructor (required for JSON deserialization)
    public Bookmark() {}

    public Bookmark(long id, String url, String title) {
        this.id = id;
        this.url = url;
        this.title = title;
    }

    // Getters and setters
    public long getId() { return id; }
    public void setId(long id) { this.id = id; }
    public String getUrl() { return url; }
    public void setUrl(String url) { this.url = url; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getTag() { return tag; }
    public void setTag(String tag) { this.tag = tag; }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ Register the Resource in Application

@Override
public void run(MyApiConfiguration config, Environment environment) {
    // Without this line, Dropwizard doesn't know about your resource!
    environment.jersey().register(new BookmarkResource());
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ง HTTP Status Codes Quick Reference

Code Meaning When to use
200 OK Successful GET, PUT
201 Created Successful POST (new resource created)
204 No Content Successful DELETE
400 Bad Request Invalid input
401 Unauthorized Not authenticated
403 Forbidden Authenticated but no permission
404 Not Found Resource doesn't exist
500 Server Error Something blew up

๐Ÿง‘โ€๐Ÿ’ป Recommended Folder Structure

src/main/java/com/example/
โ”œโ”€โ”€ MyApiApplication.java       โ† Entry point
โ”œโ”€โ”€ MyApiConfiguration.java     โ† Config class
โ”œโ”€โ”€ core/                       โ† Domain models / entities
โ”‚   โ””โ”€โ”€ Bookmark.java
โ”œโ”€โ”€ db/                         โ† Database access (DAOs)
โ”‚   โ””โ”€โ”€ BookmarkDAO.java
โ”œโ”€โ”€ resources/                  โ† REST endpoints
โ”‚   โ””โ”€โ”€ BookmarkResource.java
โ””โ”€โ”€ auth/                       โ† Authentication
    โ””โ”€โ”€ MyAuthenticator.java
Enter fullscreen mode Exit fullscreen mode

Chapter 5: Bean Validation (Hibernate Validator)

โ“ Why Validate?

Users send bad data. Always. Never trust input. Validation catches bad data before it hits your database.

Without validation:

{ "url": "", "title": null }
Enter fullscreen mode Exit fullscreen mode

This would save garbage data to your database!

๐Ÿ“ฆ Dependency (already included in dropwizard-core)

Dropwizard includes Hibernate Validator automatically. No extra dependency needed!

๐Ÿท๏ธ Common Validation Annotations

import javax.validation.constraints.*;
import org.hibernate.validator.constraints.*;

public class Bookmark {

    private long id;

    @NotEmpty(message = "URL cannot be empty")
    @URL(message = "Must be a valid URL")
    private String url;

    @NotEmpty
    @Size(min = 1, max = 200, message = "Title must be 1-200 characters")
    private String title;

    @NotNull
    private String tag;

    @Min(value = 0, message = "Views cannot be negative")
    private int views;

    @Email(message = "Must be a valid email")
    private String authorEmail;

    @Pattern(regexp = "^[A-Z].*", message = "Must start with uppercase")
    private String category;
}
Enter fullscreen mode Exit fullscreen mode

โœ… Using @Valid in Resources

Add @Valid before the parameter โ€” Dropwizard validates automatically!

@POST
public Response createBookmark(@Valid Bookmark bookmark) {
    // If bookmark fails validation, Dropwizard returns 422 Unprocessable Entity
    // with details about what's wrong โ€” automatically!
    bookmarks.add(bookmark);
    return Response.status(201).entity(bookmark).build();
}
Enter fullscreen mode Exit fullscreen mode

If validation fails, the client gets:

{
  "errors": [
    "bookmark.url may not be empty",
    "bookmark.title size must be between 1 and 200"
  ]
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ›ก๏ธ Validating Config Too!

public class MyApiConfiguration extends Configuration {

    @NotEmpty  // App won't start if login is missing in config.yml!
    @JsonProperty
    private String login;

    @NotEmpty
    @JsonProperty
    private String password;
}
Enter fullscreen mode Exit fullscreen mode

โœ๏ธ Custom Validator

// 1. Create the annotation
@Documented
@Constraint(validatedBy = NoSpacesValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoSpaces {
    String message() default "Must not contain spaces";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 2. Create the validator logic
public class NoSpacesValidator implements ConstraintValidator<NoSpaces, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext ctx) {
        return value == null || !value.contains(" ");
    }
}

// 3. Use it
public class Bookmark {
    @NoSpaces
    private String slug;
}
Enter fullscreen mode Exit fullscreen mode

Chapter 6: Jackson โ€“ JSON Serialization

๐Ÿ”„ What is Jackson?

Jackson converts between Java objects โ†” JSON. This is called serialization (Java โ†’ JSON) and deserialization (JSON โ†’ Java).

Dropwizard uses Jackson automatically for all REST responses. When your method returns a Bookmark object, Jackson converts it to JSON for you.

๐Ÿ“ฆ Dependency

Already included in dropwizard-core!

๐Ÿท๏ธ Key Jackson Annotations

import com.fasterxml.jackson.annotation.*;

public class Bookmark {

    // Changes the JSON key name
    @JsonProperty("bookmark_id")
    private long id;  // Java field "id" โ†’ JSON key "bookmark_id"

    // Include in JSON even if null
    @JsonInclude(JsonInclude.Include.ALWAYS)
    private String title;

    // Exclude from JSON output
    @JsonIgnore
    private String internalNotes;

    // Exclude null values from JSON
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String optionalField;

    // Custom date format
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createdAt;

    // Map JSON โ†’ object (deserialization constructor)
    @JsonCreator
    public Bookmark(@JsonProperty("bookmark_id") long id,
                    @JsonProperty("url") String url) {
        this.id = id;
        this.url = url;
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ—บ๏ธ Working with Unknown Fields

// Ignore any extra JSON fields you don't have a field for
@JsonIgnoreProperties(ignoreUnknown = true)
public class Bookmark {
    private String url;
    // JSON might have "extra_field" โ€” this annotation ignores it instead of throwing an error
}
Enter fullscreen mode Exit fullscreen mode

โš™๏ธ Customizing Jackson in Dropwizard

@Override
public void initialize(Bootstrap<MyApiConfiguration> bootstrap) {
    // Customize the ObjectMapper
    bootstrap.getObjectMapper()
        .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŒ Polymorphic Types (Advanced)

// Base class
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = WebBookmark.class, name = "web"),
    @JsonSubTypes.Type(value = BookBookmark.class, name = "book")
})
public abstract class Bookmark { }

public class WebBookmark extends Bookmark { private String url; }
public class BookBookmark extends Bookmark { private String isbn; }

// JSON will look like:
// { "type": "web", "url": "https://..." }
// { "type": "book", "isbn": "978-..." }
Enter fullscreen mode Exit fullscreen mode

Chapter 7: Database Access with Hibernate/JDBI

๐Ÿ—ƒ๏ธ Your Two Options in Dropwizard

Option Description Best For
Hibernate (JPA) ORM โ€” maps Java objects to DB tables Complex domain models
JDBI Thin wrapper over JDBC โ€” write SQL directly Simple, fast queries

๐Ÿ“ฆ Dependencies

<!-- For Hibernate -->
<dependency>
    <groupId>io.dropwizard</groupId>
    <artifactId>dropwizard-hibernate</artifactId>
    <version>${dropwizard.version}</version>
</dependency>

<!-- MySQL Driver (use 5.x to avoid Liquibase conflicts!) -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

๐Ÿ—„๏ธ Part A: Hibernate

Step 1 โ€” The Entity (maps to a DB table)

package com.example.core;

import javax.persistence.*;

@Entity                         // This Java class = a database table
@Table(name = "bookmarks")      // Table name in the database
@NamedQueries({
    // Pre-defined queries attached to this class
    @NamedQuery(
        name = "com.example.core.Bookmark.findAll",
        query = "SELECT b FROM Bookmark b"     // JPQL (not SQL!)
    ),
    @NamedQuery(
        name = "com.example.core.Bookmark.findByTag",
        query = "SELECT b FROM Bookmark b WHERE b.tag = :tag"
    )
})
public class Bookmark {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // Auto-increment
    private long id;

    @Column(name = "url", nullable = false)
    private String url;

    @Column(name = "title")
    private String title;

    @Column(name = "tag")
    private String tag;

    // Getters, setters, default constructor...
}
Enter fullscreen mode Exit fullscreen mode

Step 2 โ€” The DAO (Data Access Object)

package com.example.db;

import io.dropwizard.hibernate.AbstractDAO;
import com.example.core.Bookmark;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import java.util.List;
import java.util.Optional;

public class BookmarkDAO extends AbstractDAO<Bookmark> {

    public BookmarkDAO(SessionFactory factory) {
        super(factory);  // AbstractDAO handles Hibernate session management
    }

    // Get all bookmarks
    public List<Bookmark> findAll() {
        return list(namedQuery("com.example.core.Bookmark.findAll"));
    }

    // Get by ID โ€” returns Optional (empty if not found)
    public Optional<Bookmark> findById(long id) {
        return Optional.ofNullable(get(id));
    }

    // Get by tag โ€” using named query parameter
    public List<Bookmark> findByTag(String tag) {
        Query<Bookmark> query = namedQuery("com.example.core.Bookmark.findByTag");
        query.setParameter("tag", tag);
        return list(query);
    }

    // Create or update
    public Bookmark save(Bookmark bookmark) {
        return persist(bookmark);  // AbstractDAO's persist = INSERT or UPDATE
    }

    // Delete
    public void delete(Bookmark bookmark) {
        currentSession().delete(bookmark);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3 โ€” Register Hibernate Bundle in Application

public class MyApiApplication extends Application<MyApiConfiguration> {

    // Create the Hibernate bundle โ€” tell it about all your entities!
    private final HibernateBundle<MyApiConfiguration> hibernate =
        new HibernateBundle<MyApiConfiguration>(Bookmark.class) {  // list ALL entities here
            @Override
            public DataSourceFactory getDataSourceFactory(MyApiConfiguration config) {
                return config.getDataSourceFactory();
            }
        };

    @Override
    public void initialize(Bootstrap<MyApiConfiguration> bootstrap) {
        bootstrap.addBundle(hibernate);  // Register the bundle!
    }

    @Override
    public void run(MyApiConfiguration config, Environment environment) {
        // Create DAO with Hibernate session factory
        final BookmarkDAO dao = new BookmarkDAO(hibernate.getSessionFactory());

        // Pass DAO to resource
        environment.jersey().register(new BookmarkResource(dao));
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4 โ€” Config file addition

# config.yml
database:
  driverClass: com.mysql.jdbc.Driver
  url: jdbc:mysql://localhost:3306/bookmarks_db
  user: root
  password: secret
  properties:
    charSet: UTF-8
    hibernate.dialect: org.hibernate.dialect.MySQL5InnoDBDialect
  maxWaitForConnection: 1s
  validationQuery: "/* MyService Health Check */ SELECT 1"
  minSize: 2
  maxSize: 8
  checkConnectionWhileIdle: false
Enter fullscreen mode Exit fullscreen mode

Step 5 โ€” Add DataSourceFactory to Config class

public class MyApiConfiguration extends Configuration {

    @Valid
    @NotNull
    private DataSourceFactory database = new DataSourceFactory();

    @JsonProperty("database")
    public DataSourceFactory getDataSourceFactory() { return database; }

    @JsonProperty("database")
    public void setDataSourceFactory(DataSourceFactory factory) {
        this.database = factory;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6 โ€” Use @UnitOfWork in Resource

public class BookmarkResource {
    private final BookmarkDAO dao;

    public BookmarkResource(BookmarkDAO dao) {
        this.dao = dao;
    }

    @GET
    // @UnitOfWork manages the Hibernate session + transaction for you!
    // Without it, you'd have to open/close sessions manually.
    @UnitOfWork
    public List<Bookmark> getAllBookmarks() {
        return dao.findAll();
        // โš ๏ธ WARNING: Lazy-loaded fields must be accessed INSIDE this method!
    }

    @POST
    @UnitOfWork
    public Bookmark createBookmark(@Valid Bookmark bookmark) {
        return dao.save(bookmark);
    }
}
Enter fullscreen mode Exit fullscreen mode

Chapter 8: Liquibase โ€“ Database Migrations

๐Ÿค” The Problem Without Migrations

Imagine two developers both changing the database schema. Developer A adds a tag column, Developer B renames url to link. Who goes first? How do you track DB changes? How do you roll back?

Liquibase is like Git, but for your database schema.

๐Ÿ“ฆ Dependency

<dependency>
    <groupId>io.dropwizard</groupId>
    <artifactId>dropwizard-migrations</artifactId>
    <version>${dropwizard.version}</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

โš™๏ธ Register the Bundle

@Override
public void initialize(Bootstrap<MyApiConfiguration> bootstrap) {
    bootstrap.addBundle(new MigrationsBundle<MyApiConfiguration>() {
        @Override
        public DataSourceFactory getDataSourceFactory(MyApiConfiguration config) {
            return config.getDataSourceFactory();
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“„ The Migration File (migrations.xml)

Place at src/main/resources/migrations.xml:

<?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">

    <!-- Each changeSet = one atomic database change -->
    <!-- id + author combination must be globally unique! -->

    <changeSet id="1" author="mitri">
        <createTable tableName="bookmarks">
            <column name="id" type="BIGINT" autoIncrement="true">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="url" type="VARCHAR(500)">
                <constraints nullable="false"/>
            </column>
            <column name="title" type="VARCHAR(255)"/>
            <column name="tag" type="VARCHAR(100)"/>
        </createTable>
        <!-- Liquibase can auto-generate rollback for createTable (just DROP TABLE) -->
    </changeSet>

    <changeSet id="2" author="mitri">
        <addColumn tableName="bookmarks">
            <column name="created_at" type="TIMESTAMP" defaultValueDate="CURRENT_TIMESTAMP"/>
        </addColumn>
    </changeSet>

    <changeSet id="3" author="mitri">
        <!-- Insert test data โ€” must specify manual rollback! -->
        <insert tableName="bookmarks">
            <column name="url" value="https://dropwizard.io"/>
            <column name="title" value="Dropwizard Official Site"/>
            <column name="tag" value="java"/>
        </insert>
        <rollback>
            <!-- Tell Liquibase how to undo this changeSet -->
            <delete tableName="bookmarks">
                <where>url = 'https://dropwizard.io'</where>
            </delete>
        </rollback>
    </changeSet>

</databaseChangeLog>
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ Running Migrations

# Check current state of your database
java -jar my-api.jar db status config.yml

# Apply all pending migrations
java -jar my-api.jar db migrate config.yml

# Apply migrations up to a specific tag
java -jar my-api.jar db migrate --include-tag mytag config.yml

# Roll back last 1 change
java -jar my-api.jar db rollback --count 1 config.yml

# Danger zone! Drops everything. Only for dev!
java -jar my-api.jar db drop-all --confirm-delete-everything config.yml
Enter fullscreen mode Exit fullscreen mode

๐Ÿ†š Liquibase vs Flyway

Feature Liquibase Flyway
Migration format XML, YAML, SQL, JSON SQL (primarily)
Rollbacks Built-in support Manual in Flyway Pro
Author tracking Yes (prevents conflicts) No
DB independence Yes (XML format) SQL-only = DB specific
Dropwizard default โœ… Yes Via third-party bundle

Why Liquibase wins for teams: The author field prevents naming collisions. Two developers can't accidentally create conflicting migrations.


Chapter 9: Authentication

๐Ÿ” Dropwizard's Two Built-in Auth Methods

  1. Basic Auth โ€” Username + Password sent with every request
  2. OAuth 2.0 โ€” Token-based auth (used by most modern APIs)

๐Ÿ“ฆ Dependency

<dependency>
    <groupId>io.dropwizard</groupId>
    <artifactId>dropwizard-auth</artifactId>
    <version>${dropwizard.version}</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘ค Step 1 โ€” The User Class

Must extend java.security.Principal in Dropwizard 1.x:

package com.example.auth;

import java.security.Principal;

public class User implements Principal {
    private final String name;

    public User(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”‘ Step 2 โ€” The Authenticator

package com.example.auth;

import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
import java.util.Optional;

public class MyAuthenticator implements Authenticator<BasicCredentials, User> {

    private final String validLogin;
    private final String validPassword;

    public MyAuthenticator(String login, String password) {
        this.validLogin = login;
        this.validPassword = password;
    }

    @Override
    public Optional<User> authenticate(BasicCredentials credentials)
            throws AuthenticationException {

        // Check if credentials match
        if (validLogin.equals(credentials.getUsername()) &&
            validPassword.equals(credentials.getPassword())) {
            return Optional.of(new User(credentials.getUsername()));
        }

        // Return empty Optional = authentication failed
        // Dropwizard will return 401 Unauthorized automatically
        return Optional.empty();
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿญ Step 3 โ€” Register in Application

@Override
public void run(MyApiConfiguration config, Environment environment) {

    // Create the authenticator with credentials from config
    MyAuthenticator authenticator = new MyAuthenticator(
        config.getLogin(),
        config.getPassword()
    );

    // Register Basic Auth with the authenticator
    environment.jersey().register(
        new AuthDynamicFeature(
            new BasicCredentialAuthFilter.Builder<User>()
                .setAuthenticator(authenticator)
                .setRealm("My Secure API")  // Shown in browser login dialog
                .buildAuthFilter()
        )
    );

    // Enable @Auth annotation injection in resources
    environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”’ Step 4 โ€” Protect Your Endpoints

@GET
@Path("/{id}")
@UnitOfWork
// @Auth injects the authenticated user AND protects the endpoint
public Bookmark getBookmark(@Auth User user,  // user = logged in user
                             @PathParam("id") long id) {
    // Only authenticated users reach here
    return dao.findById(id)
              .orElseThrow(() -> new NotFoundException("Not found: " + id));
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ก Making a Request with Basic Auth

# Using curl โ€” -u "username:password"
curl -u "admin:secret123" http://localhost:8080/bookmarks/1

# With HTTPS (ignore self-signed cert for dev)
curl -k -u "admin:secret123" https://localhost:8443/bookmarks

# Or manually with header
curl -H "Authorization: Basic YWRtaW46c2VjcmV0MTIz" http://localhost:8080/bookmarks
Enter fullscreen mode Exit fullscreen mode

Chapter 10: Health Checks & Metrics

๐Ÿ’“ Health Checks

Health checks answer: "Is my app healthy right now?"

The admin port (8081) exposes:

  • /ping โ†’ returns "pong" if app is up
  • /healthcheck โ†’ runs all registered health checks

Built-in Health Checks

Dropwizard provides one by default:

  • DeadlockHealthCheck โ€” checks if JVM has thread deadlocks

When you add the Hibernate bundle, you get a free extra:

  • Database connectivity check โ€” tries to ping the database

Writing a Custom Health Check

package com.example.health;

import com.codahale.metrics.health.HealthCheck;

public class ExternalServiceHealthCheck extends HealthCheck {

    private final String serviceUrl;

    public ExternalServiceHealthCheck(String serviceUrl) {
        this.serviceUrl = serviceUrl;
    }

    @Override
    protected Result check() throws Exception {
        // Try to call the external service
        try {
            // ... make HTTP call to serviceUrl ...
            boolean isReachable = pingService(serviceUrl);

            if (isReachable) {
                return Result.healthy("External service is UP");
            } else {
                return Result.unhealthy("External service is DOWN at: " + serviceUrl);
            }
        } catch (Exception e) {
            return Result.unhealthy("Exception connecting: " + e.getMessage());
        }
    }

    private boolean pingService(String url) {
        // Simplified โ€” use OkHttp (Chapter 15) in real code
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Registering a Health Check

@Override
public void run(MyApiConfiguration config, Environment environment) {
    // Register your custom health check
    environment.healthChecks().register(
        "external-service",
        new ExternalServiceHealthCheck("https://api.thirdparty.com/ping")
    );
}
Enter fullscreen mode Exit fullscreen mode

Response When Healthy

curl http://localhost:8081/healthcheck
Enter fullscreen mode Exit fullscreen mode
{
  "deadlocks": { "healthy": true },
  "hibernate": { "healthy": true },
  "external-service": { "healthy": true, "message": "External service is UP" }
}
Enter fullscreen mode Exit fullscreen mode

Response When Unhealthy (returns 500)

{
  "external-service": {
    "healthy": false,
    "message": "External service is DOWN at: https://api.thirdparty.com/ping"
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Š Metrics

Dropwizard's Metrics library lets you measure things in your app:

import com.codahale.metrics.*;

public class BookmarkResource {
    private final Meter requests;      // How many requests per second
    private final Timer latency;       // How long requests take
    private final Counter activeUsers; // Count something
    private final Histogram payloadSize; // Distribution of values

    public BookmarkResource(MetricRegistry metrics, BookmarkDAO dao) {
        this.requests = metrics.meter("bookmark.requests");
        this.latency = metrics.timer("bookmark.latency");
        this.activeUsers = metrics.counter("bookmark.active-users");
    }

    @GET
    public List<Bookmark> getAllBookmarks() {
        requests.mark();  // Record a request happened
        try (Timer.Context ctx = latency.time()) {  // Measure how long this takes
            return dao.findAll();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ˆ Sending Metrics to Graphite

# config.yml
metrics:
  reporters:
    - type: graphite
      host: graphite.example.com
      port: 2003
      prefix: myapp
      frequency: 1 minute
Enter fullscreen mode Exit fullscreen mode

Chapter 11: Testing in Dropwizard

๐Ÿงช Testing Philosophy

Dropwizard takes testing seriously and provides special tools that make it easy to test REST APIs without running a full server.

๐Ÿ“ฆ Dependency

<dependency>
    <groupId>io.dropwizard</groupId>
    <artifactId>dropwizard-testing</artifactId>
    <version>${dropwizard.version}</version>
    <scope>test</scope>
</dependency>
<!-- Note: JUnit is included automatically! No need to add it separately. -->
Enter fullscreen mode Exit fullscreen mode

๐Ÿงฉ Level 1: Unit Testing a Resource (ResourceTestRule)

Tests just your resource class + Jersey, without a real database:

package com.example.resources;

import io.dropwizard.testing.junit.ResourceTestRule;
import org.junit.*;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.*;
import javax.ws.rs.core.Response;
import java.util.List;

public class BookmarkResourceTest {

    // Mock the DAO โ€” no real database needed!
    private static final BookmarkDAO dao = mock(BookmarkDAO.class);

    // Spins up an in-memory Jersey server for testing
    @ClassRule
    public static final ResourceTestRule resources = ResourceTestRule.builder()
            .addResource(new BookmarkResource(dao))
            .build();

    @Before
    public void setup() {
        // Define what the mock returns
        when(dao.findAll()).thenReturn(List.of(new Bookmark(1L, "https://example.com", "Example")));
    }

    @After
    public void tearDown() {
        reset(dao);  // Reset mock between tests
    }

    @Test
    public void testGetAllBookmarks() {
        // Make a GET request using the test client
        List<Bookmark> result = resources.client()
                .target("/bookmarks")
                .request()
                .get(new GenericType<List<Bookmark>>(){});

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getUrl()).isEqualTo("https://example.com");
        verify(dao).findAll();  // Verify DAO was called
    }

    @Test
    public void testCreateBookmark_returns201() {
        Bookmark newBookmark = new Bookmark(0, "https://new.com", "New Site");
        when(dao.save(any(Bookmark.class))).thenReturn(newBookmark);

        Response response = resources.client()
                .target("/bookmarks")
                .request()
                .post(Entity.json(newBookmark));

        assertThat(response.getStatus()).isEqualTo(201);
    }

    @Test
    public void testGetBookmark_notFound_returns404() {
        when(dao.findById(99L)).thenReturn(Optional.empty());

        Response response = resources.client()
                .target("/bookmarks/99")
                .request()
                .get();

        assertThat(response.getStatus()).isEqualTo(404);
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”— Level 2: Integration Testing (DropwizardAppRule)

Starts the entire application โ€” tests everything together:

package com.example;

import io.dropwizard.testing.junit.DropwizardAppRule;
import org.junit.*;
import javax.ws.rs.client.*;
import javax.ws.rs.core.Response;

public class BookmarkIntegrationTest {

    // Starts the ENTIRE app (real server, real database)
    @ClassRule
    public static final DropwizardAppRule<MyApiConfiguration> RULE =
        new DropwizardAppRule<>(
            MyApiApplication.class,
            "config-test.yml"  // Use test config (H2 in-memory DB)
        );

    private Client client;

    @Before
    public void setup() {
        client = ClientBuilder.newClient();
    }

    @Test
    public void testServerIsRunning() {
        Response response = client
            .target(String.format("http://localhost:%d/bookmarks",
                RULE.getLocalPort()))
            .request()
            .get();

        assertThat(response.getStatus()).isEqualTo(200);
    }

    @Test
    public void testAuthenticatedEndpoint() {
        Response response = client
            .target(String.format("https://localhost:%d/bookmarks",
                RULE.getLocalPort()))
            .request()
            // Add Basic Auth header
            .header("Authorization", "Basic " + Base64.encode("admin:secret"))
            .get();

        assertThat(response.getStatus()).isEqualTo(200);
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ—ƒ๏ธ Testing Database Queries (DAOTest)

public class BookmarkDAOTest {

    // Spins up real Hibernate with H2 in-memory database
    @ClassRule
    public static final DAOTestRule database = DAOTestRule.newBuilder()
        .addEntityClass(Bookmark.class)
        .build();

    private BookmarkDAO dao;

    @Before
    public void setUp() throws Exception {
        dao = new BookmarkDAO(database.getSessionFactory());
    }

    @Test
    public void testSaveAndFind() {
        Bookmark saved = database.inTransaction(() -> {
            Bookmark b = new Bookmark();
            b.setUrl("https://test.com");
            b.setTitle("Test");
            return dao.save(b);
        });

        assertThat(saved.getId()).isGreaterThan(0);
        assertThat(dao.findById(saved.getId())).isPresent();
    }
}
Enter fullscreen mode Exit fullscreen mode

Chapter 12: Lombok โ€“ Boilerplate Killer

๐Ÿ˜ค The Problem

Java is verbose. For every field in a class, you write:

  • A getter
  • A setter
  • Constructor(s)
  • equals() and hashCode()
  • toString()

That's 30+ lines of boring code for a simple 5-field class.

โœจ Lombok's Solution

Annotations that generate all that code at compile time. You write the fields. Lombok writes the rest.

๐Ÿ“ฆ Dependency

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>  <!-- Only needed at compile time -->
</dependency>
Enter fullscreen mode Exit fullscreen mode

Also install the Lombok plugin in IntelliJ IDEA (Settings โ†’ Plugins โ†’ search "Lombok").

๐Ÿท๏ธ Core Lombok Annotations

import lombok.*;
import lombok.extern.slf4j.Slf4j;

// @Data = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor
@Data
public class Bookmark {
    private long id;
    private String url;
    private String title;
}
// Lombok generates: getters, setters, toString(), equals(), hashCode(), constructor
Enter fullscreen mode Exit fullscreen mode

Breakdown of each annotation:

@Getter              // Generates getters for all fields
@Setter              // Generates setters for all fields
@ToString            // Generates toString() like: Bookmark(id=1, url=https://...)
@EqualsAndHashCode   // Generates equals() and hashCode() based on fields
@NoArgsConstructor   // Generates: public Bookmark() {}
@AllArgsConstructor  // Generates: public Bookmark(long id, String url, String title) {}
@RequiredArgsConstructor  // Constructor for @NonNull and final fields only
public class Bookmark {
    private long id;
    private String url;
    private String title;
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ—๏ธ @builder Pattern

Immutable objects with a fluent builder API:

@Builder
@Getter  // @Builder doesn't auto-generate getters
public class BookmarkRequest {
    private final String url;
    private final String title;
    private final String tag;
}

// Usage:
BookmarkRequest request = BookmarkRequest.builder()
    .url("https://example.com")
    .title("Example")
    .tag("java")
    .build();
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”’ Immutable Objects with @Value

// @Value = immutable version of @Data
// All fields are private final, only getters generated (no setters!)
@Value
public class UserId {
    long id;
    String username;
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ Logging with @Slf4j

@Slf4j  // Generates: private static final Logger log = LoggerFactory.getLogger(BookmarkResource.class);
public class BookmarkResource {

    @GET
    public List<Bookmark> getAll() {
        log.info("Fetching all bookmarks");  // Use log directly!
        log.debug("Debug details here");
        log.error("Something went wrong", exception);
        return dao.findAll();
    }
}
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Lombok + Jackson Integration

Jackson needs a default constructor for deserialization. Use both:

@Data
@NoArgsConstructor          // Jackson needs this
@AllArgsConstructor         // Useful for your code
@JsonIgnoreProperties(ignoreUnknown = true)
public class Bookmark {
    private long id;
    private String url;
    private String title;
}
Enter fullscreen mode Exit fullscreen mode

Or use @Builder with @Jacksonized:

@Builder
@Jacksonized  // Tells Jackson to use the builder for deserialization
@Value
public class Bookmark {
    long id;
    String url;
    String title;
}
Enter fullscreen mode Exit fullscreen mode

Chapter 13: MapStruct โ€“ Object Mapping

๐Ÿ—บ๏ธ The Mapping Problem

In real applications, you have different object types:

  • Entity โ€” maps to database (has Hibernate annotations)
  • DTO (Data Transfer Object) โ€” what you send/receive in API
  • Domain Model โ€” internal business logic object

You need to convert between them constantly. Manually writing conversion code is error-prone and tedious.

// Manual mapping โ€” boring, error-prone
public BookmarkDTO toDTO(Bookmark entity) {
    BookmarkDTO dto = new BookmarkDTO();
    dto.setId(entity.getId());
    dto.setUrl(entity.getUrl());
    dto.setTitle(entity.getTitle());
    // Forget one field and you have a bug!
    return dto;
}
Enter fullscreen mode Exit fullscreen mode

โœจ MapStruct Solution

MapStruct generates these conversion methods at compile time using annotations.

๐Ÿ“ฆ Dependency

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.5.Final</version>
    <scope>provided</scope>
</dependency>
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ง Maven Plugin (required with Lombok)

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.30</version>
            </path>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.5.Final</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ Define Your Objects

// Database Entity
@Entity
@Data
public class BookmarkEntity {
    @Id @GeneratedValue
    private Long id;
    private String url;
    private String title;
    private String tag;
    private Date createdAt;
    private String internalNotes;  // Should NOT be in API response!
}

// API DTO (Data Transfer Object)
@Data
@NoArgsConstructor
public class BookmarkDTO {
    private Long id;
    private String url;
    private String title;
    private String tag;
    // Notice: no internalNotes โ€” we don't expose this!
    private String createdDate;  // Different format than entity!
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ—บ๏ธ The Mapper Interface

package com.example.mapper;

import org.mapstruct.*;

@Mapper(componentModel = "default")  // Use "cdi" for CDI, "spring" for Spring
public interface BookmarkMapper {

    // Simple mapping โ€” same field names map automatically!
    BookmarkDTO toDTO(BookmarkEntity entity);

    // Map from DTO back to entity
    BookmarkEntity toEntity(BookmarkDTO dto);

    // Custom field mapping
    @Mapping(source = "createdAt", target = "createdDate",
             dateFormat = "yyyy-MM-dd")  // Convert Date to formatted String
    @Mapping(target = "internalNotes", ignore = true)  // Don't map this field
    BookmarkDTO toDTOCustom(BookmarkEntity entity);

    // Map a list automatically!
    List<BookmarkDTO> toDTOList(List<BookmarkEntity> entities);
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿญ Getting the Mapper Instance

// Approach 1: Factory method (no DI)
BookmarkMapper mapper = Mappers.getMapper(BookmarkMapper.class);

// Approach 2: With Guice (Chapter 14)
// Bind in your module, inject where needed
Enter fullscreen mode Exit fullscreen mode

โœ… Using the Mapper in Resource

@Path("/bookmarks")
public class BookmarkResource {
    private final BookmarkDAO dao;
    private final BookmarkMapper mapper = Mappers.getMapper(BookmarkMapper.class);

    @GET
    @UnitOfWork
    public List<BookmarkDTO> getAllBookmarks() {
        List<BookmarkEntity> entities = dao.findAll();
        return mapper.toDTOList(entities);  // Entity โ†’ DTO, one line!
    }

    @POST
    @UnitOfWork
    public BookmarkDTO createBookmark(@Valid BookmarkDTO dto) {
        BookmarkEntity entity = mapper.toEntity(dto);  // DTO โ†’ Entity
        BookmarkEntity saved = dao.save(entity);
        return mapper.toDTO(saved);  // Entity โ†’ DTO for response
    }
}
Enter fullscreen mode Exit fullscreen mode

Chapter 14: Google Guice โ€“ Dependency Injection

๐Ÿค” The Problem

As your app grows, you end up with this mess in your run() method:

public void run(Config config, Environment env) {
    HibernateBundle hib = hibernate;
    BookmarkDAO dao = new BookmarkDAO(hib.getSessionFactory());
    BookmarkMapper mapper = Mappers.getMapper(BookmarkMapper.class);
    MyAuthenticator auth = new MyAuthenticator(config.getLogin(), config.getPassword());
    BookmarkResource resource = new BookmarkResource(dao, mapper, auth);
    env.jersey().register(resource);
    // What if BookmarkResource needs 5 more dependencies?!
}
Enter fullscreen mode Exit fullscreen mode

Every class manually creates its dependencies. Change one thing โ†’ change everywhere.

โœจ Guice Solution

Inversion of Control: Instead of classes creating their dependencies, Guice creates them and injects them where needed.

๐Ÿ“ฆ Dependency

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>5.1.0</version>
</dependency>
<!-- Dropwizard + Guice integration -->
<dependency>
    <groupId>ru.vyarus</groupId>
    <artifactId>dropwizard-guicey</artifactId>
    <version>5.7.1</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ Step 1 โ€” Mark Dependencies with @Inject

// BookmarkDAO depends on SessionFactory
public class BookmarkDAO extends AbstractDAO<BookmarkEntity> {

    @Inject  // Guice will provide SessionFactory
    public BookmarkDAO(SessionFactory factory) {
        super(factory);
    }
}

// BookmarkResource depends on DAO and Mapper
public class BookmarkResource {

    private final BookmarkDAO dao;
    private final BookmarkMapper mapper;

    @Inject  // Guice will inject both!
    public BookmarkResource(BookmarkDAO dao, BookmarkMapper mapper) {
        this.dao = dao;
        this.mapper = mapper;
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“‹ Step 2 โ€” The Guice Module (Wiring Configuration)

package com.example;

import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;

public class MyModule extends AbstractModule {

    @Override
    protected void configure() {
        // Bind interface โ†’ implementation
        // "Whenever someone asks for UserService, give them UserServiceImpl"
        bind(UserService.class).to(UserServiceImpl.class).in(Singleton.class);

        // Bind a class directly
        bind(BookmarkDAO.class).in(Singleton.class);

        // Bind mapper
        bind(BookmarkMapper.class).toInstance(Mappers.getMapper(BookmarkMapper.class));
    }

    // @Provides methods create instances that need configuration
    @Provides
    @Singleton
    public BookmarkDAO provideBookmarkDAO(SessionFactory factory) {
        return new BookmarkDAO(factory);
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿญ Step 3 โ€” Register with Dropwizard

Using dropwizard-guicey:

public class MyApiApplication extends GuiceApplication<MyApiConfiguration> {

    @Override
    public void initialize(Bootstrap<MyApiConfiguration> bootstrap) {
        super.initialize(bootstrap);
        bootstrap.addBundle(hibernate);
    }

    @Override
    public void run(MyApiConfiguration config, Environment environment) {
        // Resources are auto-discovered and injected!
    }

    @Override
    protected Injector createInjector(MyApiConfiguration config) {
        return Guice.createInjector(new MyModule());
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿท๏ธ Key Guice Annotations

@Inject     // Mark constructor/field/method for injection
@Singleton  // One instance for entire app lifetime
@Named("dbUrl")  // Disambiguate when multiple bindings of same type exist

// Usage:
@Inject @Named("dbUrl") String databaseUrl;
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”„ Guice Scopes

bind(BookmarkDAO.class).in(Singleton.class);    // One instance per app
bind(BookmarkDAO.class).in(RequestScoped.class); // New instance per HTTP request
bind(BookmarkDAO.class);                         // Default: new instance every time
Enter fullscreen mode Exit fullscreen mode

Chapter 15: OkHttp โ€“ HTTP Client

๐ŸŒ Why OkHttp?

Microservices talk to each other over HTTP. You need an HTTP client to:

  • Call third-party APIs (payment, email, maps)
  • Call other microservices in your system

OkHttp is the most popular Java HTTP client โ€” fast, simple, feature-rich.

๐Ÿ“ฆ Dependency

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ Basic GET Request

import okhttp3.*;
import java.io.IOException;

public class ExternalApiClient {

    // IMPORTANT: Reuse the same OkHttpClient โ€” it manages connection pooling!
    private final OkHttpClient client = new OkHttpClient();
    private final ObjectMapper objectMapper = new ObjectMapper();

    public String fetchData(String url) throws IOException {
        // Build the request
        Request request = new Request.Builder()
            .url(url)
            .header("Accept", "application/json")
            .header("Authorization", "Bearer my-token")
            .build();

        // Execute synchronously
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected response: " + response);
            }
            return response.body().string();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ค POST Request with JSON Body

public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

public Bookmark createRemoteBookmark(BookmarkDTO dto) throws IOException {
    String json = objectMapper.writeValueAsString(dto);

    Request request = new Request.Builder()
        .url("https://api.other-service.com/bookmarks")
        .post(RequestBody.create(json, JSON))  // POST with JSON body
        .build();

    try (Response response = client.newCall(request).execute()) {
        String responseBody = response.body().string();
        return objectMapper.readValue(responseBody, Bookmark.class);
    }
}
Enter fullscreen mode Exit fullscreen mode

โฑ๏ธ Timeouts & Configuration

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)  // How long to wait to connect
    .readTimeout(30, TimeUnit.SECONDS)     // How long to wait for data
    .writeTimeout(15, TimeUnit.SECONDS)    // How long to wait for upload
    .retryOnConnectionFailure(true)        // Retry on network errors
    .build();
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”„ Async Requests

public void fetchDataAsync(String url, Callback callback) {
    Request request = new Request.Builder().url(url).build();

    // Non-blocking โ€” callback is called when done
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            log.error("Request failed", e);
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            try (response) {
                String body = response.body().string();
                // Process response
            }
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”Œ OkHttp Interceptors (Middleware)

Interceptors run for every request โ€” perfect for logging, auth headers, etc.:

// Logging interceptor
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(chain -> {
        Request request = chain.request();
        log.info("โ†’ {} {}", request.method(), request.url());

        long start = System.currentTimeMillis();
        Response response = chain.proceed(request);
        long duration = System.currentTimeMillis() - start;

        log.info("โ† {} {} ({}ms)", response.code(), request.url(), duration);
        return response;
    })
    .addInterceptor(chain -> {
        // Auth interceptor โ€” add token to every request
        Request authenticated = chain.request().newBuilder()
            .header("Authorization", "Bearer " + getToken())
            .build();
        return chain.proceed(authenticated);
    })
    .build();
Enter fullscreen mode Exit fullscreen mode

Chapter 16: Hystrix โ€“ Circuit Breaker

โšก The Cascading Failure Problem

Service A calls Service B. Service B calls Service C. Service C goes down.

Without circuit breakers:

  1. Service C times out after 30 seconds
  2. Service B waits 30s, then times out
  3. Service A waits 30s too = everything hangs for 30+ seconds!
  4. Threads pile up, memory fills up, your whole system crashes

๐Ÿ”Œ The Circuit Breaker Pattern

Like an electrical circuit breaker โ€” when failures exceed a threshold, it "trips" and subsequent calls fail immediately instead of waiting.

States:

  • CLOSED โ€” Normal operation, calls pass through
  • OPEN โ€” Too many failures, all calls fail immediately
  • HALF-OPEN โ€” Testing if service recovered; allows one call through

๐Ÿ“ฆ Dependency

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.18</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

โ˜• Wrapping Calls with HystrixCommand

package com.example.hystrix;

import com.netflix.hystrix.*;
import java.io.IOException;

public class FetchBookmarkCommand extends HystrixCommand<String> {

    private final String bookmarkId;
    private final ExternalApiClient apiClient;

    public FetchBookmarkCommand(String bookmarkId, ExternalApiClient apiClient) {
        super(HystrixCommandGroupKey.Factory.asKey("BookmarkService"));
        this.bookmarkId = bookmarkId;
        this.apiClient = apiClient;
    }

    @Override
    protected String run() throws Exception {
        // The actual (potentially failing) call
        return apiClient.fetchData("https://api.service.com/bookmarks/" + bookmarkId);
    }

    @Override
    protected String getFallback() {
        // This runs when:
        // 1. run() throws an exception
        // 2. Circuit is OPEN (previous failures)
        // 3. Timeout occurs
        return "{\"id\":\"" + bookmarkId + "\",\"status\":\"unavailable\"}";
    }
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŽฏ Using the Command

// Synchronous execution
String result = new FetchBookmarkCommand(bookmarkId, client).execute();

// Asynchronous (returns Future)
Future<String> future = new FetchBookmarkCommand(bookmarkId, client).queue();

// Reactive (returns Observable)
Observable<String> observable = new FetchBookmarkCommand(bookmarkId, client).observe();
Enter fullscreen mode Exit fullscreen mode

โš™๏ธ Configuring Hystrix

HystrixCommand.Setter config = HystrixCommand.Setter
    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("BookmarkService"))
    .andCommandKey(HystrixCommandKey.Factory.asKey("FetchBookmark"))
    .andCommandPropertiesDefaults(
        HystrixCommandProperties.Setter()
            .withExecutionTimeoutInMilliseconds(3000)  // 3 second timeout
            .withCircuitBreakerRequestVolumeThreshold(10)  // Min requests before circuit can trip
            .withCircuitBreakerErrorThresholdPercentage(50)  // Trip if 50% fail
            .withCircuitBreakerSleepWindowInMilliseconds(5000)  // Wait 5s before trying again
    );

// Use in command:
public FetchBookmarkCommand(String id, ExternalApiClient client) {
    super(config);  // Pass config to super
    this.bookmarkId = id;
    this.apiClient = client;
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ๏ธ HystrixCommand + OkHttp (Combined Example)

@Slf4j
public class ExternalServiceClient {

    private final OkHttpClient httpClient;
    private final ObjectMapper objectMapper;

    @Inject
    public ExternalServiceClient(OkHttpClient httpClient, ObjectMapper objectMapper) {
        this.httpClient = httpClient;
        this.objectMapper = objectMapper;
    }

    public BookmarkDTO getBookmark(String id) {
        // Wrap the OkHttp call in Hystrix
        return new HystrixCommand<BookmarkDTO>(
            HystrixCommandGroupKey.Factory.asKey("ExternalBookmarks")) {

            @Override
            protected BookmarkDTO run() throws IOException {
                Request request = new Request.Builder()
                    .url("https://external.api.com/bookmarks/" + id)
                    .build();

                try (Response response = httpClient.newCall(request).execute()) {
                    return objectMapper.readValue(response.body().string(), BookmarkDTO.class);
                }
            }

            @Override
            protected BookmarkDTO getFallback() {
                log.warn("Fallback triggered for bookmark: {}", id);
                BookmarkDTO fallback = new BookmarkDTO();
                fallback.setTitle("Unavailable");
                return fallback;
            }
        }.execute();
    }
}
Enter fullscreen mode Exit fullscreen mode

Chapter 17: ๐Ÿ”ฒ TODO โ€” Advanced Topics to Study Next

These topics weren't covered in this guide but are important for production-ready applications:

๐Ÿ” Security

  • [ ] OAuth 2.0 โ€” Token-based authentication (JWT tokens)
  • [ ] HTTPS/TLS โ€” Configure SSL in Dropwizard config.yml
  • [ ] LDAP Authentication โ€” Enterprise authentication via directory services
  • [ ] CORS โ€” Cross-Origin Resource Sharing configuration

๐Ÿ“Š Observability

  • [ ] Distributed Tracing โ€” Zipkin/Jaeger for tracing requests across microservices
  • [ ] Prometheus โ€” Modern metrics collection (alternative to Graphite)
  • [ ] Grafana Dashboards โ€” Visualizing your metrics
  • [ ] Structured Logging โ€” JSON logs for log aggregation (ELK Stack)
  • [ ] OpenTelemetry โ€” Unified observability standard

๐Ÿ—๏ธ Architecture

  • [ ] Authorization โ€” @RolesAllowed, @PermitAll annotations
  • [ ] Rate Limiting โ€” Prevent API abuse
  • [ ] API Versioning โ€” Strategies for versioning your REST API
  • [ ] HATEOAS โ€” Hypermedia-driven REST (links in responses)
  • [ ] GraphQL โ€” Alternative to REST for flexible queries
  • [ ] OpenAPI/Swagger โ€” Auto-generate API documentation

๐Ÿ—„๏ธ Database

  • [ ] JDBI โ€” Lighter-weight alternative to Hibernate
  • [ ] Connection Pooling โ€” HikariCP configuration
  • [ ] Read Replicas โ€” Routing reads vs writes
  • [ ] Database Sharding โ€” Horizontal scaling

๐Ÿณ Deployment

  • [ ] Docker โ€” Containerizing your Fat JAR
  • [ ] Kubernetes โ€” Orchestrating containers
  • [ ] CI/CD โ€” GitHub Actions / Jenkins pipelines
  • [ ] Blue/Green Deployment โ€” Zero-downtime deployments
  • [ ] Service Discovery โ€” Consul, Eureka

โšก Performance

  • [ ] Async Resources โ€” @Suspended and AsyncResponse in JAX-RS
  • [ ] Caching โ€” Redis/Memcached with Dropwizard
  • [ ] Connection Timeout Tuning โ€” Jetty thread pool settings
  • [ ] GraalVM Native Image โ€” Compile to native binary for faster startup

๐Ÿงช Advanced Testing

  • [ ] Consumer-Driven Contract Testing โ€” Pact framework
  • [ ] WireMock โ€” Mock external HTTP services in tests
  • [ ] TestContainers โ€” Real databases in Docker for integration tests
  • [ ] Chaos Engineering โ€” Testing failure scenarios (Chaos Monkey)

๐Ÿ“จ Messaging

  • [ ] Apache Kafka โ€” Event streaming
  • [ ] RabbitMQ โ€” Message queuing
  • [ ] Dropwizard Kafka Bundle โ€” Integration

Top comments (0)