Dropwizard Health Checks: Registering, Verifying, and Keeping Them Cheap
Register Dropwizard health checks that actually exercise dependencies, expose them safely via the admin servlet, and verify both the 200 and 500 paths before production.
27 Dec 2025, 10:17 UTC

A load balancer can only route around a sick instance if something tells it the instance is sick. Dropwizard's built-in health check mechanism is that something: you register named checks on the environment, the admin servlet aggregates them at /healthcheck, and the endpoint returns HTTP 200 when everything passes or 500 when anything fails. This guide covers writing a check that actually detects failure (not just process liveness), registering it, restricting who can see it, and verifying both the healthy and unhealthy paths before you rely on it in production.
What you need before starting
- A Dropwizard application with the default admin servlet enabled (it is by default, on port 8081).
- Access to your
Applicationsubclass'srun()method, where registration happens. - Knowledge of which dependency genuinely determines readiness — usually the primary database.
Package names below assume the classic com.codahale.metrics health API used by Dropwizard 1.x/2.x. Newer Dropwizard releases moved to io.dropwizard.metrics5; check the import against the version pinned in your build file before copying.
Write a check that exercises the dependency
The most common mistake is a health check that only confirms the connection pool object exists. A pool can be full of dead connections. Instead, run a trivial validation query so a broken network path or restarted database surfaces immediately.
import com.codahale.metrics.health.HealthCheck;
public class DatabaseHealthCheck extends HealthCheck {
private final WidgetDao dao;
public DatabaseHealthCheck(WidgetDao dao) {
this.dao = dao;
}
@Override
protected Result check() {
try {
dao.ping(); // e.g. SELECT 1 via JDBI
return Result.healthy();
} catch (Exception e) {
return Result.unhealthy(e);
}
}
}Register it in run() with a stable, lowercase name — monitoring systems and alerting rules key off this string, so renaming it later breaks dashboards:
@Override
public void run(AppConfiguration config, Environment environment) {
WidgetDao dao = jdbi.onDemand(WidgetDao.class);
environment.healthChecks().register("database", new DatabaseHealthCheck(dao));
}Keep the admin port private
The health endpoint leaks internal dependency details (exception messages, check names) to anyone who can reach it. Bind the admin connector to localhost or an internal interface in your YAML config:
server:
applicationConnectors:
- type: http
port: 8080
adminConnectors:
- type: http
port: 8081
bindHost: 127.0.0.1If your load balancer or orchestrator must poll the endpoint from another host, bind to a private interface and restrict it further with firewall rules rather than exposing it publicly.
Keep checks cheap
/healthcheck executes every registered check on every request. During an outage, load balancers and orchestrators poll more aggressively, so a check that makes a slow external HTTP call can amplify load exactly when the system is weakest. Keep checks to in-process state and fast validation queries. If you must check an external service, cache the result for a few seconds rather than calling it per request.
Verify both paths
Start the application and query the endpoint from the same host:
curl -i http://localhost:8081/healthcheckExpect HTTP 200 and a JSON body containing an entry per registered name, each marked healthy. Then force a failure — stop the database, or point the pool at a closed port — and repeat the request. You should get HTTP 500 with the database entry unhealthy and an error message. If you still get 200, the check is not exercising the dependency you think it is.
Also unit test the check class directly, without the server: construct it with a working DAO and assert check().isHealthy(), then with a DAO that throws and assert the unhealthy result. This catches logic errors faster than repeated server restarts.
Limitations to plan around
- A failing check changes nothing by itself. The signal only matters if a load balancer, Kubernetes probe, or monitoring system is actually configured to poll the endpoint and act on the 500.
- The endpoint is all-or-nothing: one failing non-critical check returns 500 for the whole app. Register only checks that should genuinely remove the instance from rotation; use metrics for everything else.
- Exact package names and module coordinates vary between Dropwizard versions — confirm against your pinned version before shipping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.