← Knowledge hub
Tutorialby Uptime Room· September 24, 2026· 6 min read

Spring Boot Health Check: Groups, Details and Probes

A Spring Boot health check ships working but says one word. Here is how to turn on details, split liveness from readiness, and keep the output private.

A Spring Boot health check is mostly configuration, not code. Adding the Actuator starter gives you a working endpoint immediately. The real work is subtraction: deciding how much truth it tells, splitting it for the orchestrator, and keeping the detailed version off the public internet.

The default response is one word

Add spring-boot-starter-actuator to the build and restart, and GET /actuator/health answers straight away. It answers with a single field.

{"status":"UP"}

That is not a bug. management.endpoint.health.show-details defaults to never, so the composite result appears and every contributing check stays hidden. People copy a tutorial, compare their output to its screenshot, and assume the indicators failed to register.

The status also drives the HTTP code, which is the part a monitor cares about. UP returns 200, DOWN and OUT_OF_SERVICE return 503, and UNKNOWN returns 200. A dependency being unreachable therefore changes the status line, not just the body.

Turning on the details

Three values are available and only one of them is a sensible default for a service with a public route.

ValueWho sees the detailUse it when
nevernobodythe endpoint is reachable from outside
when-authorizedauthenticated callers with the right roleSpring Security is configured
alwaysanyone who can reach the portthe management port is on a private network
management:
  endpoints:
    web:
      exposure:
        include: health,info
  endpoint:
    health:
      show-details: when-authorized

Only health is exposed over HTTP by default, which is the right call. Adding env, beans or configprops to that include list publishes your configuration, so add endpoints one at a time and know why each is there.

Which indicators come for free

Spring auto-configures contributors from what it finds on the classpath. You get most of a dependency check without writing anything.

Indicator keyRegisters whenWhat it proves
dba DataSource bean existsa connection can be borrowed and validated
diskSpacealwaysfree space is above a threshold, 10MB by default
redisSpring Data Redis is presentthe server answers an info command
mongoSpring Data MongoDB is presenta ping command succeeds
rabbitSpring AMQP is presenta broker version can be read
kafkaSpring for Apache Kafka is presentthe cluster reports a controller
pingalwaysthe application is serving requests

The Actuator health API reference lists the full set and the response shape. Disable any of them individually with management.health.<key>.enabled=false when a contributor is slower or flakier than the thing it guards.

Writing a custom indicator that earns its place

Implement HealthIndicator and Spring registers the bean, deriving the key from the class name minus the suffix. The class below appears under paymentGateway.

@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {

    private final PaymentClient client;

    PaymentGatewayHealthIndicator(PaymentClient client) {
        this.client = client;
    }

    @Override
    public Health health() {
        try {
            client.ping(Duration.ofMillis(500));
            return Health.up().withDetail("gateway", "reachable").build();
        } catch (Exception e) {
            return Health.down(e).withDetail("gateway", "unreachable").build();
        }
    }
}

Two rules keep custom contributors useful. Give every call a timeout shorter than whatever polls the endpoint, or a slow dependency turns your probe into a queue. And think hard before marking a third-party API DOWN, because that hands an outside vendor the power to restart your pods.

We prefer reporting a degraded third party as UP with a detail field, and alerting on that detail separately. The orchestrator keeps the service running and a human still finds out. Baeldung's walkthrough of health indicators covers the aggregation rules if you want a custom status ordering.

How do liveness and readiness split a Spring Boot health check?

Into two endpoints with different audiences. Liveness answers "should this process be killed and restarted", and readiness answers "should traffic be routed here right now". Merging them is how a database blip turns into a restart loop.

Spring models this with health groups. On Kubernetes the probes auto-enable; anywhere else you switch them on yourself.

management:
  endpoint:
    health:
      probes:
        enabled: true
      group:
        liveness:
          include: livenessState
        readiness:
          include: readinessState,db,redis

That gives you /actuator/health/liveness and /actuator/health/readiness. Notice what liveness excludes. A restart cannot fix an unreachable database, so the database belongs in readiness only. The Spring team's post on liveness and readiness probes explains the state model behind those two built-in contributors, and the wider pattern is covered in our notes on Kubernetes probes.

Keeping the endpoint off the public internet

Set management.server.port to something other than your application port and the whole management surface moves with it. Bind that port to a private interface and show-details: always becomes safe, because nothing outside the network can reach it.

management:
  server:
    port: 8081
    address: 127.0.0.1

This is where the actuator health endpoint and a public probe target stop being the same thing. Detailed output names your broker, your disk headroom and your datasource state. That is a gift to anyone scanning your hosts, and it is why the reference on Actuator endpoints treats exposure as an opt-in.

For the outside world, expose a thin route on the application port that returns 200 or 503 and no body detail. A controller that calls HealthEndpoint and maps the status is about ten lines.

Spring Security complicates this slightly. The management port is not covered by your application's filter chain unless you say so, and when-authorized needs a chain to authorise against. In practice we tested three arrangements and settled on the simplest: management on a loopback-bound port with always, and a separate unauthenticated route for the probe.

That split also survives a reorganisation. Moving the detailed endpoint later changes one property, while the public path stays stable for whatever is already checking it.

Watching it from outside the cluster

Internal probes decide restarts and routing. They cannot tell you whether a customer reaches the service, because everything they test sits on the same side of the ingress, the certificate and the DNS record.

An external check closes that. Point an api monitor at the thin public route, not at the detailed actuator health endpoint, and let cross-region confirmation decide when to page.

curl -X POST https://uptimeroom.com/api/v1/monitors \
  -H "Authorization: Bearer urm_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "orders service",
    "type": "api",
    "target": "https://api.example.com/healthz",
    "intervalSec": 60,
    "expectedStatus": [200],
    "regions": ["london", "singapore"],
    "alertAfterCycles": 2
  }'

Two regions on the free tier as of September 2026 is enough to stop one bad network path paging anybody. Region codes and their egress addresses are published on the probe IPs page when your ingress filters by source. Choosing thresholds for the endpoint itself is covered in our guide to API monitoring.

Common mistakes

Putting every dependency in the liveness group. A queue outage then restarts healthy pods, which lengthens the incident. Liveness should include almost nothing beyond livenessState.

Shipping show-details: always on the application port. Free disk bytes and broker versions are reconnaissance. Either move the management port or switch to when-authorized.

Letting an indicator block. A contributor with no timeout holds the response open, and the orchestrator reads a timeout as a failure. Every outbound call inside health() needs an explicit deadline.

Treating 200 as proof. UNKNOWN also returns 200. If a contributor is misconfigured and reports nothing, a status-code check passes while the dependency is unverified.

A Spring Boot health check is finished when three consumers each get what they need. The orchestrator gets liveness, the load balancer gets readiness with real dependencies behind it, and the outside world gets a status code and nothing else.

The free plan covers that third consumer for ten services as of September 2026, so the external half can be wired before the internal one is finished. Our Django equivalent shows how much more work this is on a framework that ships no Actuator.

FAQ

Which is the default health check URL in Spring Boot?

It is /actuator/health, served on the application port once spring-boot-starter-actuator is on the classpath. Health is the only endpoint exposed over HTTP by default. If you set management.server.port the whole management surface moves to that port, and the path stays the same on the new one.

How do I enable health indicators in Spring Boot?

Most register themselves when the matching library is on the classpath, so a DataSource gives you db and Spring Data Redis gives you redis. To actually see them in the response, set management.endpoint.health.show-details to when-authorized or always, because the default of never returns only the aggregate status.

What does the Spring Boot health endpoint return?

A status and, when details are enabled, one entry per contributor. UP maps to HTTP 200, DOWN and OUT_OF_SERVICE map to 503, and UNKNOWN maps to 200. The aggregate is the worst status among all contributors, so one failing dependency takes the whole response down.

How do I do a Spring Boot health check without Actuator?

Write a controller that returns 200 and does the dependency checks yourself, which is roughly what Actuator automates. It is a reasonable choice for a small service that wants no extra surface area. For anything with a database, a cache and an orchestrator, the health groups Actuator provides are cheaper than rebuilding them.

How do I check API health status from outside?

Expose a thin route that returns a status code and no detail, then point a scheduled external check at it from more than one region. Confirming a failure in a second region before alerting stops one bad network path from paging someone. Internal probes cannot see ingress, certificate or DNS faults.

Related reading