Configuration, Profiles & Actuator

Health Checks & Info Endpoints

18 min Lesson 7 of 13

Health Checks & Info Endpoints

When a service runs in production — behind a load balancer, inside a Kubernetes cluster, or managed by a container orchestrator — the infrastructure needs a reliable, machine-readable way to ask "is this instance healthy?". Spring Boot Actuator answers that need with two out-of-the-box endpoints: /actuator/health and /actuator/info. This lesson shows you exactly what they expose, how to control what they reveal, and how to write your own custom health indicator so the platform has full visibility into every dependency your application relies on.

The /actuator/health Endpoint

Add the Actuator starter to your pom.xml and the /actuator/health endpoint becomes available immediately:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>

A bare GET request to that URL returns a minimal JSON object:

{ "status": "UP" }

The status can be UP, DOWN, OUT_OF_SERVICE, or UNKNOWN. The overall status is the worst status among all registered health indicators — if your database is DOWN, the whole endpoint reports DOWN, and load balancers that poll this URL will remove the instance from rotation.

Why the minimal response by default? Exposing internal details (database host, connection counts, disk paths) to any anonymous caller is a security risk. Boot ships with a conservative default: just the overall status. You widen the response only for authenticated or internal traffic.

Showing Component Details

To see each component's individual status, set one or both of these properties in application.properties:

# Show full detail to everyone (fine for internal services or local dev) management.endpoint.health.show-details=always # Or: show detail only to authenticated users management.endpoint.health.show-details=when-authorized # Show component names even without full detail management.endpoint.health.show-components=always

With show-details=always a typical response looks like this:

{ "status": "UP", "components": { "db": { "status": "UP", "details": { "database": "PostgreSQL", "validationQuery": "isValid()" } }, "diskSpace": { "status": "UP", "details": { "total": 499963174912, "free": 341024587776, "threshold": 10485760 } }, "ping": { "status": "UP" } } }

Spring Boot auto-configures a health indicator for every technology it detects: DataSource, Redis, RabbitMQ, Kafka, MongoDB, Elasticsearch, and more. You get this coverage for free; you only need to write custom indicators for dependencies Boot does not know about.

Liveness and Readiness Probes

Kubernetes and other orchestrators distinguish two health concepts. Liveness asks "should this container be restarted?" (is the JVM stuck in a deadlock?). Readiness asks "should this instance receive traffic?" (is the application fully started and its dependencies reachable?). Boot exposes both as sub-paths of /actuator/health:

# application.properties management.health.livenessstate.enabled=true management.health.readinessstate.enabled=true

Or enable them both at once with:

management.endpoint.health.probes.enabled=true

Boot then exposes /actuator/health/liveness and /actuator/health/readiness separately. Point your Kubernetes liveness probe at the first URL and your readiness probe at the second.

Production Kubernetes pattern: never point the liveness probe at a URL that checks external dependencies. A transient database blip should not cause a cascade of pod restarts. Put external dependency checks in the readiness probe only.

Writing a Custom Health Indicator

Imagine your application calls an external payment gateway. Boot has no built-in indicator for it. Implement the HealthIndicator interface and register a Spring bean:

import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class PaymentGatewayHealthIndicator implements HealthIndicator { private final PaymentGatewayClient client; public PaymentGatewayHealthIndicator(PaymentGatewayClient client) { this.client = client; } @Override public Health health() { try { boolean reachable = client.ping(); // lightweight check if (reachable) { return Health.up() .withDetail("url", client.getBaseUrl()) .withDetail("latencyMs", client.lastPingLatency()) .build(); } return Health.down() .withDetail("reason", "ping returned false") .build(); } catch (Exception ex) { return Health.down(ex) .withDetail("url", client.getBaseUrl()) .build(); } } }

Boot names the component after the bean, stripping the HealthIndicator suffix. The bean above appears in the health response as "paymentGateway". You get full control of the details map and can pass any serializable value.

Keep health checks fast and lightweight. The /actuator/health endpoint is polled continuously by orchestrators and load balancers — sometimes every few seconds. A health check that runs a complex query or waits on a slow network call will degrade performance and make the overall status unreliable. Use a cheap ping, a SELECT 1, or a lightweight status API call.

Reactive Health Indicators

If your application uses Spring WebFlux (reactive stack), implement ReactiveHealthIndicator instead so the check does not block the event loop:

import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.ReactiveHealthIndicator; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; @Component public class ExternalCacheHealthIndicator implements ReactiveHealthIndicator { private final ReactiveCacheClient cacheClient; public ExternalCacheHealthIndicator(ReactiveCacheClient cacheClient) { this.cacheClient = cacheClient; } @Override public Mono<Health> health() { return cacheClient.ping() .map(ok -> Health.up().withDetail("cache", "reachable").build()) .onErrorResume(ex -> Mono.just(Health.down(ex).build())); } }

The /actuator/info Endpoint

/actuator/info surfaces static metadata about your deployed artifact — version, build timestamp, git commit hash, custom properties. It is invaluable during an incident: one glance tells you exactly which build is running on every node without logging into any server.

First, expose the endpoint (it is disabled by default since Boot 2.6):

# application.properties management.endpoints.web.exposure.include=health,info management.info.env.enabled=true

Then add key/value pairs under the info.* namespace:

info.app.name=Order Service info.app.version=2.4.1 info.app.team=platform info.contact.email=platform@example.com

The response mirrors the structure of these keys:

{ "app": { "name": "Order Service", "version": "2.4.1", "team": "platform" }, "contact": { "email": "platform@example.com" } }

Build and Git Info via Maven/Gradle Plugins

The real power of /actuator/info comes from combining it with build plugins. The Spring Boot Maven plugin can generate a build-info.properties file at build time, and the git-commit-id plugin can embed the exact git SHA:

<!-- pom.xml — inside <build><plugins> --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals><goal>build-info</goal></goals> </execution> </executions> </plugin> <plugin> <groupId>io.github.git-commit-id</groupId> <artifactId>git-commit-id-maven-plugin</artifactId> <version>9.0.0</version> <executions> <execution> <goals><goal>revision</goal></goals> </execution> </executions> </plugin>

Enable the contributors in application.properties:

management.info.build.enabled=true management.info.git.enabled=true management.info.git.mode=full

Now /actuator/info includes the build time, artifact version from your POM, the git branch, commit SHA, commit time, and whether the working tree was dirty at build time. A response might look like:

{ "build": { "artifact": "order-service", "version": "2.4.1", "time": "2024-11-15T09:23:41.000Z", "group": "com.example" }, "git": { "branch": "main", "commit": { "id": "a3f8c12", "time": "2024-11-15T09:15:00.000Z", "message": { "short": "fix: handle null order ID" } }, "dirty": false } }

Custom InfoContributor

For dynamic info — say, the number of active feature flags or a runtime configuration summary — implement InfoContributor:

import org.springframework.boot.actuate.info.Info; import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.stereotype.Component; @Component public class FeatureFlagInfoContributor implements InfoContributor { private final FeatureFlagService flags; public FeatureFlagInfoContributor(FeatureFlagService flags) { this.flags = flags; } @Override public void contribute(Info.Builder builder) { builder.withDetail("featureFlags", flags.getActiveFlags()); } }

Summary

/actuator/health gives your infrastructure an authoritative liveness/readiness signal. Auto-configured indicators cover standard integrations; custom HealthIndicator beans cover everything else. Keep checks cheap and focused. /actuator/info embeds build and git metadata into the running artifact, turning every deployment into a self-describing artefact. Together, these two endpoints form the essential observability contract between your application and the platform it runs on. The next lesson expands the picture with Micrometer metrics.

ES
Edrees Salih
1 hour ago

We are still cooking the magic in the way!