Spring Boot health check endpoint with Actuator
Add spring-boot-starter-actuator and Spring Boot serves /actuator/health for you, returning 200 while every contributor is UP and 503 the moment one of them goes DOWN.
Actuator is the answer and most Spring Boot services already have half of it wired up. Add spring-boot-starter-actuator to the build file and /actuator/health starts serving on the application port. Health is the only endpoint exposed over HTTP by default, so you get the one you want and none of the ones you would rather not publish. The auto-configuration also registers a contributor for every dependency it recognises: a db indicator that borrows a connection from your DataSource, plus Redis, MongoDB, RabbitMQ and disk space. You do not write the database check.
The part that gets skipped is the status code, and it is the only part a monitor reads. Actuator maps DOWN and OUT_OF_SERVICE to 503 by default and leaves UP on 200. A health URL that answers 200 from a JVM whose connection pool has been empty for ten minutes turns an outage into a green dashboard and a support thread. Actuator gets this right without configuration: the DataSource cannot hand out a connection, db goes DOWN, the response is 503. Logdash marks a monitor down on any status code outside 200 to 399, so that 503 is the difference between a chart you read afterwards and a message on your phone while it is happening.
Expose it and decide what the body says
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: when-authorized
show-components: when-authorized
probes:
enabled: true management.endpoint.health.show-details defaults to never, so an anonymous caller sees a status word and nothing else. Keep it that way on a public URL. Set it to always and the body starts naming your database vendor, the free space on the disk and the exception message from whatever just broke, which is a handy page for you and a better one for someone scanning your domain. Setting when-authorized gives your team the detail and leaves the public response as one word. A monitor only reads the status code, so it loses nothing either way. Turning probes on splits /actuator/health/liveness from /actuator/health/readiness, which is what Kubernetes should use so a pod is not restarted because Postgres blinked.
A HealthIndicator for what Actuator cannot see
Actuator knows about the infrastructure it auto-configured. It does not know that your app is pointless without the payments API, or that a consumer has fallen twenty minutes behind. Implement HealthIndicator, return Health.up() or Health.down(), and one DOWN contributor takes the whole endpoint to 503. The bean name becomes the key in the JSON, so PaymentsHealthIndicator appears as payments. One import to watch: Spring Boot 4 moved the interface to org.springframework.boot.health.contributor, and 3.x still uses org.springframework.boot.actuate.health.
@Component
class PaymentsHealthIndicator implements HealthIndicator {
private final PaymentsClient payments;
PaymentsHealthIndicator(PaymentsClient payments) {
this.payments = payments;
}
@Override
public Health health() {
try {
payments.ping(); // one cheap call, not a real transaction
return Health.up().build();
} catch (Exception ex) {
return Health.down().withDetail("reason", "payments").build();
}
}
} What to check and what to leave out
- One call per dependency you cannot serve a request without. The built-in db indicator already covers the DataSource, so your own select 1 on top of it doubles the work.
- Nothing that takes longer than a second. Contributors run on every request to the endpoint, and the Logdash pinger gives up after 10 seconds and records the check as down.
- No fan-out to services you do not own. A health check that calls three partner APIs turns their bad afternoon into your 3am alert.
- No secrets, no build metadata, no stack traces in the body. That is what show-details is for, and never is the safe value on a public URL.
- Readiness for the load balancer, liveness for the kubelet. Pointing both at the same URL means a slow database restarts your pods.
Point a monitor at it
- 1 Add the URL Create a project in Logdash and point its HTTP monitor at https://yourapp.com/actuator/health. Every check stores the status code and the response time, so the latency chart builds itself from the first ping.
- 2 Pick the interval Checks run every 5 minutes on the free plan, every minute on Builder and every 15 seconds on Pro. One monitor per project, and the free plan covers five projects, which is usually one per service.
- 3 Take the database away Stop Postgres and leave the app running. The db contributor flips to DOWN, /actuator/health answers 503, the monitor goes red on the next check and a Telegram message arrives naming the endpoint and the status code.
The gotcha: nothing can reach it from outside
If you moved Actuator onto its own port with management.server.port, the health URL is no longer on the port your load balancer publishes and an external monitor cannot see it. The other one that bites is ingress rewriting a 503 into a branded error page served with a 200, which puts you back where you started. Curl the public URL with -i before you trust the monitor.
What is the Spring Boot health check endpoint?
GET /actuator/health, served on the application port once spring-boot-starter-actuator is on the classpath. It aggregates every registered contributor and reports the worst status among them.
What is the Actuator health check URL on a deployed app?
Your base URL plus the servlet context path plus /actuator/health. management.server.port moves it to another port, management.endpoints.web.base-path relocates /actuator, and path-mapping.health renames the endpoint. None of those is security.
Should I expose Actuator publicly?
Exposing health is fine with show-details set to never or when-authorized, because the public body is one word. Do not open the rest of the tree: env, heapdump and loggers on a public URL are a real problem.
What status code does Actuator return when the app is down?
503. DOWN and OUT_OF_SERVICE both map to SERVICE_UNAVAILABLE by default, UP and UNKNOWN stay on 200. Any monitor that reads status codes will see the failure without extra configuration.