Dropwizard Health Checks: Use Readiness for Dependencies, Not Liveness
Dropwizard aggregates all health checks into one /healthcheck response. Pointing both Kubernetes liveness and readiness probes at it can restart pods on transient database failures. Use /healthcheck for readiness and a process-level signal for liveness.
25 Nov 2025, 11:26 UTC

The problem: one health endpoint, two Kubernetes jobs
You run a Dropwizard service on Kubernetes. The admin port (default 8081) exposes /healthcheck, and you point both the liveness and readiness probes at it. Your health check includes a database connection test. A transient database blip makes /healthcheck return HTTP 500. Kubelet treats that as a liveness failure, kills the pod, and a restart storm can follow. The check behaved exactly as coded; the probe assignment did not.
What Dropwizard actually provides
Dropwizard's HealthCheck interface has one method, check(), which returns Result.healthy() or Result.unhealthy(message). You register checks with environment.healthChecks().register(name, check). The framework aggregates them on the admin port at /healthcheck: HTTP 200 when every check passes, HTTP 500 when any check fails. Checks run on demand when the endpoint is requested; Dropwizard core does not schedule periodic checks by itself.
Core Dropwizard does not provide built-in query-parameter filtering that splits /healthcheck into separate liveness and readiness views. All registered checks are aggregated into one response. That means you cannot safely point a liveness probe at /healthcheck if any registered check touches an external dependency.
Worked example: separate dependency checks from process checks
The practical decision is to use /healthcheck for readiness only, and use a dependency-free signal for liveness. This example uses the Dropwizard database module (dropwizard-db, which provides DataSourceFactory).
public class DatabaseHealthCheck extends HealthCheck {
private final DataSource dataSource;
public DatabaseHealthCheck(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
protected Result check() throws Exception {
try (Connection conn = dataSource.getConnection()) {
try (Statement stmt = conn.createStatement()) {
stmt.execute("SELECT 1");
}
return Result.healthy();
} catch (SQLException e) {
return Result.unhealthy("Database connection failed: " + e.getMessage());
}
}
}
Register it in your application's run() method. The check name appears in the /healthcheck JSON response.
@Override
public void run(AppConfig config, Environment environment) {
DataSource dataSource = config.getDataSourceFactory()
.build(environment.metrics(), "postgresql");
environment.healthChecks().register("database", new DatabaseHealthCheck(dataSource));
}
For liveness, do not register a database-dependent check on the same endpoint if you plan to query it with a liveness probe. Instead, point Kubernetes at a signal that only reflects the local process.
Kubernetes probe configuration
Use an HTTP readiness probe against the admin endpoint, and an liveness probe that does not depend on /healthcheck. Example manifest snippets:
readinessProbe:
httpGet:
path: /healthcheck
port: 8081
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
Apply the manifest with kubectl apply -f deployment.yaml from a terminal with cluster access. The readiness probe stops sending traffic when the database check fails, but the liveness probe keeps the pod running. If you prefer an exec probe, replace tcpSocket with a command that confirms the Java process is running, such as ["sh", "-c", "pgrep java"]; verify that sh and pgrep exist in your container image first.
Trade-offs and limits
- Admin port exposure:
/healthcheckshares the admin port with endpoints such as/threadsand/heap. Do not expose the admin port to the public internet. Use a Kubernetes NetworkPolicy to limit which pods can reach port 8081 while allowing the node's kubelet to probe it. Binding the admin connector tolocalhostinside the container will break kubelet HTTP probes, which target the pod IP. - No built-in probe splitting: Because
/healthcheckaggregates every registered check, a failing database check fails the entire endpoint. Use it for readiness, not liveness, unless you add custom servlet logic to filter checks. - Check latency:
check()runs on the admin request thread. Keep checks fast. A simple connection test should complete well under one second. Slow checks delay admin responses and monitoring scrapes. Avoid running migrations or external API calls insidecheck(). - No automatic remediation: An unhealthy result returns HTTP 500 but does not shut down the application or remove it from service discovery by itself. Your orchestration layer must consume the endpoint.
Verification checklist
- Start the Dropwizard application locally. Run
curl -s -i localhost:8081/healthcheckin a terminal. Expect HTTP 200 and a JSON body with per-check results. - Stop the database. Run the same curl command. Expect HTTP 500 and the database check showing
"healthy": false. - In Kubernetes, temporarily block database access from the pod. Confirm the readiness probe fails while the liveness probe continues to pass and the pod is not restarted.
- Check your
pom.xmlorbuild.gradlefor the Dropwizard version and confirm theHealthCheckAPI and admin endpoint behavior match your release. The examples assume current 2.x/3.x core behavior; verify query handling and status codes in your version's release notes.
Rollback
This change modifies application code and Kubernetes manifests only. To roll back, revert the run() registration and restore the original probe paths in the deployment manifest, then redeploy. No database state is changed by the health check itself.
Closing action
Open your Dropwizard application class, identify every check registered in run(), and ask whether it belongs on readiness or liveness. Move dependency checks to /healthcheck for readiness, and point liveness at a process-level signal. That split prevents a transient database outage from triggering a pod restart storm.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.