Spring Boot Actuator: Health Checks, Metrics, Info Endpoints, Custom Indicators
Monitor Spring Boot apps with Actuator: built-in health checks, metrics, info endpoints, and how to build custom health indicators.
Monitor Spring Boot apps with Actuator: built-in health checks, metrics, info endpoints, and how to build custom health indicators. The guide uses practical examples to explain introduction to spring boot actuator, enabling actuator and endpoint configuration and shows how to apply the ideas in a Spring Boot project. It closes with common pitfalls and production checks so you can apply the pattern with fewer surprises.
Introduction to Spring Boot Actuator
Spring Boot Actuator is a sub-project of Spring Boot that provides production-ready features to help you monitor and manage your application. It offers built-in endpoints that expose operational information about your running application: health status, metrics, environment details, bean listings, and more.
When you deploy an application to production, you need visibility into its internal state. Actuator fills that gap. It works with modern observability stacks and gives your operations team the hooks they need to validate application health, track performance, and respond to incidents.
Actuator gives you:
- Built-in endpoints for common monitoring needs
- Extensibility through custom health indicators and metrics
- Integration with Micrometer for metrics collection
- Security options to control endpoint exposure
- Flexibility to expose data in various formats (JSON, HTML)
Enabling Actuator and Endpoint Configuration
Adding Actuator to Your Project
Add the Actuator starter to your pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
Or in build.gradle:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}
Exposing Endpoints
By default, only the health endpoint is visible. You control which endpoints are exposed using the management.endpoints.web.exposure.include property:
# Expose all endpoints
management.endpoints.web.exposure.include=*
# Expose specific endpoints only
management.endpoints.web.exposure.include=health,metrics,info
# Exclude specific endpoints
management.endpoints.web.exposure.exclude=env,beans
Endpoint Configuration
Each endpoint can be configured individually:
# Enable/disable an endpoint
management.endpoint.health.enabled=true
management.endpoint.metrics.enabled=true
# Set cache time-to-live for endpoint responses
management.endpoint.health.cache.time-to-live=10s
# Show/hide details in health endpoint
management.endpoint.health.show-details=always
management.endpoint.health.show-details=when_authorized
management.endpoint.health.show-details=never
Base Path Configuration
Change the base path from /actuator to something else:
management.endpoints.web.base-path=/manage
Health Endpoint Deep Dive
The health endpoint is often the first thing platform teams integrate with load balancers and orchestration tools. It aggregates health information from multiple sources into a single response.
Health Endpoint Response
A typical health response looks like this:
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "PostgreSQL",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 512000000000,
"free": 423000000000,
"threshold": 104857600
}
},
"ping": {
"status": "UP"
}
}
}
Built-in Health Indicators
Spring Boot auto-configures health indicators for common infrastructure components:
| Indicator | Activated When | Check |
|---|---|---|
DiskSpaceHealthIndicator |
Always | Available disk space above threshold |
DataSourceHealthIndicator |
DataSource bean present |
Connection pool is valid |
RedisHealthIndicator |
Redis libraries on classpath | Redis connection successful |
MongoHealthIndicator |
MongoDB libraries present | MongoDB connection successful |
RabbitHealthIndicator |
RabbitMQ libraries present | RabbitMQ connection successful |
ElasticsearchRestHealthIndicator |
Elasticsearch client present | Elasticsearch cluster reachable |
SolrHealthIndicator |
Solr client present | Solr instance reachable |
Aggregate Health
When multiple components report health, Spring Boot rolls them up into a final status:
DOWN → OUT_OF_SERVICE → UP
The worst status wins. If any critical component is DOWN, the entire application reports DOWN.
Custom Health Status
You can define custom statuses beyond the default UP and DOWN:
management.health.status.order=DOWN,OUT_OF_SERVICE,UP,UNKNOWN
@Bean
public HealthStatusHttpResultStatusConverter healthStatusHttpResultStatusConverter() {
return new HealthStatusHttpResultStatusConverter();
}
Metrics Endpoint with Micrometer
Actuator uses Micrometer as its metrics facade. It provides a vendor-neutral interface for collecting metrics.
Accessing Metrics
Query the metrics endpoint:
GET /actuator/metrics/jvm.memory.used
GET /actuator/metrics/http.server.requests
GET /actuator/metrics/process.cpu.usage
Available Metrics
Spring Boot auto-configures numerous metrics across several domains:
JVM Metrics
jvm.memory.used- Memory pool usagejvm.memory.max- Maximum memoryjvm.gc.pause- GC pause timesjvm.threads.states- Thread counts by state
HTTP Metrics
http.server.requests- Request count, latency, error rates- Tagged with
uri,method,status,outcome
Tomcat Metrics
tomcat.sessions.active.current- Active sessionstomcat.threads.current- Current threads
Custom Metrics
You can register custom metrics using Micrometer:
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
@Component
public class CustomMetrics {
private final Timer orderProcessingTimer;
private final MeterRegistry meterRegistry;
public CustomMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.orderProcessingTimer = Timer.builder("order.processing")
.description("Time taken to process orders")
.tag("type", "standard")
.register(meterRegistry);
}
public void recordOrderProcessing(Runnable processing) {
orderProcessingTimer.record(processing);
}
public <T> T recordOrderProcessing(Supplier<T> processing) {
return orderProcessingTimer.recordSupplier(processing);
}
}
Tags and Dimensions
You can add dimensions to your metrics for filtering:
counter = meterRegistry.counter("api.requests",
"method", "GET",
"endpoint", "/users",
"status", "success");
Prometheus Integration
Expose metrics in Prometheus format:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.export.prometheus.enabled=true
Info Endpoint Customization
The info endpoint exposes arbitrary application information.
Auto-populated Info
Spring Boot auto-contributes info when certain dependencies are present:
git- Git commit information (requiresgit-commit-id-plugin)build- Build information (automatic with Maven/Gradle)
Custom Info Contributors
You can implement InfoContributor to add custom data:
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class CustomInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("application", Map.of(
"name", "Order Service",
"version", "2.1.0",
"environment", "production"
));
}
}
Info Endpoint Output
{
"application": {
"name": "Order Service",
"version": "2.1.0",
"environment": "production"
},
"git": {
"commit": {
"id": "a1b2c3d",
"time": "2026-06-15T10:30:00Z"
}
}
}
Environment Info
management.info.env.enabled=true
management.info.java.enabled=true
management.info.os.enabled=true
Custom Health Indicators Implementation
Custom health indicators let you define what “healthy” means for your specific application.
Implementing a Health Indicator
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
public DatabaseHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Health health() {
try (Connection connection = dataSource.getConnection()) {
boolean valid = connection.isValid(5);
if (valid) {
return Health.up()
.withDetail("database", connection.getCatalog())
.withDetail("timeout", "5s")
.build();
}
return Health.down()
.withDetail("error", "Connection validation failed")
.build();
} catch (SQLException e) {
return Health.down()
.withDetail("error", e.getMessage())
.withException(e)
.build();
}
}
}
Reactive Health Indicators
For reactive applications, use ReactiveHealthIndicator:
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class ExternalApiHealthIndicator implements ReactiveHealthIndicator {
private final WebClient webClient;
@Override
public Mono<Health> health() {
return webClient.get()
.uri("/health")
.retrieve()
.toEntity(String.class)
.map(response -> Health.up().build())
.onErrorResume(e -> Mono.just(Health.down()
.withDetail("error", e.getMessage())
.build()));
}
}
Grouping Health Indicators
You can define groups of indicators that you check together:
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HealthGroupConfig {
@Bean
public HealthEndpoint.ApplicationSonarHealthDetailsFunction customHealthGroup() {
return details -> {
// Custom logic to group health indicators
return details.getComponents().values().stream()
.filter(c -> c.getStatus().getCode().equals("UP"))
.count() > 0;
};
}
}
management.endpoint.health.group.custom.include=db,redis,externalApi
management.endpoint.health.group.custom.show-details=always
When to Use / When NOT to Use
When to Use Actuator
Actuator is the right tool when you need:
- Container orchestration integration: Kubernetes liveness and readiness probes require a reliable health endpoint. Actuator’s
/healthprovides exactly what orchestrators expect. - Metrics collection for Grafana/Prometheus: Micrometer integration with Actuator’s
/metricsendpoint gives you JVM metrics, HTTP metrics, and custom business metrics in a format your dashboards can consume. - Operational visibility without broad access: When you need to give operations teams health and metrics access without granting database logins or server access, Actuator provides a safe read-only channel.
- Centralized configuration auditing: The
/envand/configpropsendpoints let you audit what configuration is actually active without peering into production servers. - Startup verification: The
/infoendpoint exposes build git information and version data that helps confirm which version is actually running.
When NOT to Use Actuator
Actuator is the wrong tool when:
- You need full distributed tracing: Actuator gives you endpoints and numerical summaries, not request traces. Use Spring Cloud Sleuth or OpenTelemetry for distributed tracing.
- You need detailed profiling: Actuator’s metrics are aggregated counters and timers. For flame graphs and detailed performance profiling, use async-profiler or Java Flight Recorder.
- You need structured business event logging: Actuator tracks infrastructure metrics. For business events like “order placed” or “user registered”, use structured logging with MDC context.
- Security sensitive environments with strict compliance: Without careful configuration,
/envand/beansendpoints expose internal structure. If you cannot adequately restrict access, leave these endpoints hidden. - Real-time streaming of logs: Actuator has no log streaming capability. Use dedicated log aggregation with ELK, Loki, or cloud-native solutions.
Hybrid Approach
Actuator excels at infrastructure monitoring. For complete observability, combine it with distributed tracing (for request flows) and structured logging (for business events). Actuator tells you the app is healthy; tracing tells you why a request was slow; logs tell you what happened.
Common Pitfalls
Exposing Actuator Endpoints Publicly
Avoid exposing all Actuator endpoints in production without authentication:
# WRONG - exposes everything
management.endpoints.web.exposure.include=*
# CORRECT - explicit allowlist
management.endpoints.web.exposure.include=health,metrics,prometheus
Ignoring Health Endpoint Cache
The health endpoint caches its response by default. This can mask transient failures:
# Reduce cache TTL for faster detection of issues
management.endpoint.health.cache.time-to-live=0
Missing Custom Health Indicators for Critical Dependencies
If your application depends on an external service that is not auto-monitored, add a custom health indicator. Do not assume Kubernetes will catch every failure.
Not Configuring Readiness vs Liveness
Mixing up liveness and readiness probes causes confusion:
- Liveness = Is the application process alive?
- Readiness = Can the application accept traffic?
management.endpoint.health.group.liveness.include=ping
management.endpoint.health.group.readiness.include=db,redis,mq
Metrics Cardinality Explosion
Adding high-cardinality tags to metrics can cause memory issues:
// WRONG - userId creates unbounded cardinality
meterRegistry.counter("api.calls", "userId", userId);
// CORRECT - use low-cardinality tags
meterRegistry.counter("api.calls", "userType", userType);
Security Notes
Protecting Actuator Endpoints
Option 1: Require Authentication
management.endpoints.web.exposure.include=health,metrics
management.endpoint.health.require-ssl=false
spring.security.basic.enabled=true
Option 2: IP Whitelisting with Firewall
Configure your network firewall or API gateway to restrict access to /actuator/** to only authorized IPs.
Option 3: Custom Security Configuration
@Configuration
@SecurityScheme(
name = "actuator",
type = SecuritySchemeType.HTTP,
scheme = "basic"
)
public class ActuatorSecurityConfig {
@Bean
public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/actuator/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
);
return http.build();
}
}
Sensitive Endpoints
The following endpoints expose sensitive information and should be restricted:
/actuator/env- Environment properties/actuator/beans- Application context beans/actuator/configprops- Configuration properties/actuator/heapdump- Heap dump download
# Never expose in production
management.endpoints.web.exposure.exclude=env,beans,configprops,heapdump,threaddump
SSL Configuration
Use HTTPS for Actuator endpoints in production:
management.server.ssl.enabled=true
management.server.ssl.key-store=classpath:keystore.p12
management.server.ssl.key-store-password=${KEYSTORE_PASSWORD}
Implementation Snippets
Prometheus Metrics with Labels
@Service
public class OrderService {
private final MeterRegistry registry;
public OrderService(MeterRegistry registry) {
this.registry = registry;
}
public void processOrder(Order order) {
Timer.Sample sample = Timer.start(registry);
try {
// Process order logic
registry.counter("orders.processed",
"status", "success",
"region", order.getRegion()
).increment();
} catch (Exception e) {
registry.counter("orders.processed",
"status", "failure",
"region", order.getRegion(),
"error", e.getType()
).increment();
throw e;
} finally {
sample.stop(Timer.builder("order.processing.time")
.tag("region", order.getRegion())
.register(registry));
}
}
}
Custom Health Check with Timeout
@Component
public class TimeoutHealthIndicator implements HealthIndicator {
private final RestTemplate restTemplate;
@Override
public Health health() {
try {
ResponseEntity<String> response = restTemplate.getForEntity(
"https://external-api.com/health",
String.class
);
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up().build();
}
return Health.down()
.withDetail("statusCode", response.getStatusCode().value())
.build();
} catch (ResourceAccessException e) {
return Health.down()
.withDetail("error", "Connection timeout")
.withDetail("service", "external-api")
.build();
}
}
}
Health Indicator for Database Replication Lag
@Component
public class ReplicationLagHealthIndicator extends AbstractHealthIndicator {
private final DataSource dataSource;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
try (Connection connection = dataSource.getConnection();
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication")) {
if (rs.next()) {
long lagBytes = rs.getLong(1);
long lagMB = lagBytes / (1024 * 1024);
builder.up().withDetail("replicationLagMB", lagMB);
if (lagMB > 100) {
builder.status("OUT_OF_SERVICE")
.withDetail("warning", "Replication lag exceeds threshold");
}
} else {
builder.unknown().withDetail("info", "No replication configured");
}
}
}
}
Multi-step Readiness Check
@Component
public class CompositeReadinessCheck implements HealthIndicator {
private final List<ReadinessCheck> checks;
public CompositeReadinessCheck(List<ReadinessCheck> checks) {
this.checks = checks;
}
@Override
public Health health() {
Map<String, Health> results = new HashMap<>();
boolean allHealthy = true;
for (ReadinessCheck check : checks) {
Health h = check.check();
results.put(check.name(), h);
if (h.getStatus() != Status.UP) {
allHealthy = false;
}
}
Health.Builder builder = allHealthy ? Health.up() : Health.down();
builder.withDetails(results.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey(),
e -> (Object) e.getValue().getDetails()
)));
return builder.build();
}
}
Observability Checklist
Use this checklist to verify your Actuator implementation is production-ready:
- Health endpoint returns accurate status based on all critical dependencies
- Liveness and readiness probes are configured correctly for Kubernetes
- Metrics are being collected and forwarded to your monitoring system
- Custom metrics include appropriate tags for filtering and aggregation
- Info endpoint exposes build and version information for traceability
- Health endpoint cache TTL is set appropriately for your tolerance
- Sensitive Actuator endpoints are protected or hidden
- Prometheus metrics endpoint is available (if using Prometheus)
- Health groups are defined for different probe types
- Custom health indicators cover all external service dependencies
- Health endpoint response times are acceptable under load
- Logging is configured to include correlation IDs for metrics
- Alerting is configured based on health status changes
- Dashboard panels exist for key business metrics from Micrometer
Trade-off Table
| Aspect | Default Behavior | Production Consideration |
|---|---|---|
| Health endpoint exposure | Only health visible |
Explicitly list required endpoints |
| Health details | Hidden by default | Show for authorized users only |
| Metrics cache | Cached, may hide transients | Consider time-to-live=0 for faster alerting |
| Endpoint base path | /actuator |
May need customization for gateway routing |
| Auto-configured indicators | Covers common infra | Always add custom indicators for business dependencies |
| Cardinality in tags | Low cardinality by default | Avoid user IDs, request IDs as tags |
| Decision | Pros | Cons |
|---|---|---|
Expose all endpoints (*) |
Simple initial setup | Security risk, information disclosure |
| Detailed health info | Better debugging | Potential information leakage |
| High-frequency health checks | Faster incident detection | Increased load on monitoring infrastructure |
| Custom health groups | Targeted probe checks | Additional configuration maintenance |
Quick Recap Checklist
- Add
spring-boot-starter-actuatordependency - Configure exposed endpoints via
management.endpoints.web.exposure.include - Implement custom
HealthIndicatorfor business-critical dependencies - Set up separate liveness and readiness probe groups
- Configure health endpoint cache TTL appropriately
- Add custom metrics with low-cardinality tags via Micrometer
- Customize info endpoint with build and git information
- Protect sensitive endpoints with authentication or firewall rules
- Add health indicators for external service dependencies
- Test health endpoint response under failure conditions
- Configure Prometheus metrics export if using Prometheus
- Set up alerting on health status changes
- Document which endpoints are exposed and their purpose
Failure Scenarios
Scenario 1: Database Connection Pool Exhaustion
Symptom: Health endpoint returns UP but the application hangs on database operations.
Root Cause: DataSourceHealthIndicator validates connections but does not check pool saturation.
Mitigation:
@Component
public class ConnectionPoolHealthIndicator implements HealthIndicator {
@Override
public Health health() {
HikariDataSource hikari = (HikariDataSource) dataSource;
int active = hikari.getHikariPoolMXBean().getActiveConnections();
int max = hikari.getMaximumPoolSize();
double utilization = (double) active / max;
Health.Builder builder = Health.up()
.withDetail("activeConnections", active)
.withDetail("maxConnections", max)
.withDetail("utilization", String.format("%.2f%%", utilization * 100));
if (utilization > 0.9) {
return builder
.status("OUT_OF_SERVICE")
.withDetail("warning", "Connection pool near capacity")
.build();
}
return builder.build();
}
}
Scenario 2: Stale Health Information Due to Caching
Symptom: Application reports UP for several seconds after a database failure.
Root Cause: The default health cache TTL is 60 seconds.
Fix:
management.endpoint.health.cache.time-to-live=5s
Scenario 3: Metrics Cardinality Explosion
Symptom: Gradual memory increase in the application, high cardinality in Prometheus.
Root Cause: Custom metrics using high-cardinality tags like userId, sessionId, requestId.
Fix: Use low-cardinality tags, or use MeterFilter to deny high-cardinality metrics:
@Bean
public MeterFilter highCardinalityFilter() {
return MeterFilter.deny(id -> {
if (id.getName().startsWith("http.requests")) {
return id.getTags().stream()
.anyMatch(tag -> tag.getKey().equals("userId"));
}
return false;
});
}
Interview Questions
Liveness probes determine if the application process is healthy and should be restarted. Readiness probes determine if the application can accept traffic. In Kubernetes terms, a failing liveness probe triggers a pod restart, while a failing readiness probe removes the pod from Service endpoints. With Spring Boot Actuator, you configure these as separate health groups: liveness should be minimal (just ping), while readiness should verify all dependencies like databases, caches, and message queues that are required to handle requests.
Implement the HealthIndicator interface and annotate with @Component. The interface has a single method health() that returns a Health object. In the method, perform your check logic and return Health.up().withDetail(...).build() on success or Health.down().withDetail(...).build() on failure. Spring Boot registers it automatically with the health endpoint. For reactive applications, implement ReactiveHealthIndicator instead, returning a Mono<Health>.
Micrometer is a vendor-neutral metrics facade that provides a unified interface for collecting and exporting metrics. Spring Boot Actuator uses Micrometer as its metrics backend and auto-configures meters for JVM metrics, HTTP metrics, and any infrastructure components present. You access metrics via the /actuator/metrics endpoint. To add custom metrics, inject the MeterRegistry and use its builder methods for counters, timers, gauges, and more.
Security should be layered. First, use management.endpoints.web.exposure.exclude to hide sensitive endpoints like /env, /beans, and /heapdump. Second, implement authentication on exposed endpoints using Spring Security with a security filter chain matching /actuator/**. Third, restrict IP access at the network level or API gateway. Finally, use HTTPS for production Actuator traffic. The health endpoint can typically be left public for load balancer checks, but metrics and info endpoints should require authentication.
Common pitfalls include: exposing all endpoints publicly via management.endpoints.web.exposure.include=*, relying on default health indicators without adding custom ones for business-critical dependencies, ignoring health endpoint cache settings that mask transient failures, creating high-cardinality metrics tags that cause memory issues, not distinguishing between liveness and readiness probes, and failing to test health indicators under failure conditions. Many teams also forget that the health endpoint only reports component status, not the underlying cause of degradation—for that you need proper logging and distributed tracing.
Health status aggregation follows a worst-status-wins pattern defined by management.health.status.order. The default order is DOWN,OUT_OF_SERVICE,UP,UNKNOWN. When multiple health indicators report status, Spring Boot iterates through this list and returns the first non-UP status it encounters. For example, if any component reports DOWN, the overall status is DOWN. If no component reports DOWN but one reports OUT_OF_SERVICE, the overall status is OUT_OF_SERVICE. Unknown components are ignored in this calculation unless all components are unknown.
HealthIndicator is the synchronous interface used in traditional Spring MVC applications. The health() method blocks while performing its check and returns a Health object directly. ReactiveHealthIndicator is used in reactive (WebFlux) applications and returns a Mono<Health> or Flux<Health>, allowing non-blocking health checks. Spring Boot automatically routes to the appropriate interface based on the application type. You should not implement both in the same component.
Configure cache TTL via management.endpoint.health.cache.time-to-live. The default is 60 seconds. A non-zero cache means health checks are not performed on every request, reducing overhead, but it also means transient failures may not be immediately visible. Set TTL to 0 for immediate health status updates when you need fast failure detection, or increase it for high-traffic applications where health checks themselves could become a bottleneck. Consider the latency tolerance of your monitoring consumers when setting this value.
Tags (also called dimensions) are added when creating meters via the MeterRegistry. For counters: meterRegistry.counter("api.calls", "method", "GET", "status", "200"). For timers: Timer.builder("request.duration").tag("endpoint", "/users").register(meterRegistry). Tags enable filtering and aggregation in monitoring systems. Always use low-cardinality values—never use userId, sessionId, or requestId as tag values. Common low-cardinality tags include method, uri, status, region, and service.
Spring Boot auto-configures health indicators when specific dependencies are on the classpath: DataSourceHealthIndicator (any JDBC DataSource), RedisHealthIndicator (Spring Data Redis), MongoHealthIndicator (Spring Data MongoDB), RabbitHealthIndicator (Spring AMQP), ElasticsearchRestHealthIndicator (Elasticsearch client), SolrHealthIndicator (Solr client), DiskSpaceHealthIndicator (always), PingHealthIndicator (always), and others for Couchbase,InfluxDB, Neo4j, and more. Each indicator only activates when its corresponding library is detected.
Add custom statuses by configuring management.health.status.order in your application.properties: management.health.status.order=DOWN,OUT_OF_SERVICE,UP,UNKNOWN,CUSTOM_STATUS. Then in your health indicator, return the custom status: Health.status("CUSTOM_STATUS"). You can also register a HealthStatusHttpResultStatusConverter bean to map custom statuses to specific HTTP response codes for load balancer integration.
The /actuator/info endpoint exposes arbitrary JSON data from InfoContributor implementations. Spring Boot auto-contributes git information (via git-commit-id-plugin) and build information (Maven/Gradle). Implement InfoContributor to add custom data: call builder.withDetail("key", object) to add any serializable data. Info is read from application.properties with management.info.*.enabled=true flags for java, env, and os details.
In Spring Boot 2.3+, enable probes via management.endpoint.health.probes.enabled=true. This exposes /actuator/health/liveness and /actuator/health/readiness as separate endpoints. The liveness probe uses the liveness health group (by default only includes ping), while readiness uses the readiness group (includes db, redis, etc.). Kubernetes uses these to decide pod lifecycle: liveness failures trigger restart, readiness failures prevent traffic routing.
Test health indicators by calling the health() method directly or via the health endpoint. Use mocking to simulate failure conditions: mock the dependency (database, external service) and configure it to throw exceptions or return invalid responses. For integration testing, use @AutoConfigureMockHealthIndicator or test the actual endpoint with TestRestTemplate. Always test both the success path (Health.up()) and failure path (Health.down()), including timeout scenarios.
Add the Prometheus registry dependency: micrometer-registry-prometheus. Then expose the prometheus endpoint: management.endpoints.web.exposure.include=prometheus. The /actuator/prometheus endpoint returns metrics in Prometheus text format. Configure management.metrics.export.prometheus.enabled=true for additional options like pushgateway integration. Prometheus scrapes this endpoint at regular intervals and stores the time-series data for Grafana visualization.
Spring Boot Admin is an open-source project from codecentric that provides a web UI for monitoring Spring Boot applications. Applications register as clients by adding the spring-boot-admin-client dependency and configuring the admin server URL. The client then pushes Actuator data (health, metrics, env, loggers) to the admin server, which aggregates and displays it. Alternatively, the admin server can use Eureka or Consul for service discovery to find applications to monitor.
Cardinality explosion occurs when using high-uniqueness tag values like userId, sessionId, or requestId. Prevention: use MeterFilter beans to deny or rename high-cardinality metrics. Example: MeterFilter.deny(id -> id.getTags().stream().anyMatch(t -> t.getKey().equals("userId"))). Alternatively, use low-cardinality tag values like userType (premium/basic) instead of userId. If you need per-request granularity, use distributed tracing (Zipkin, Sleuth) instead of metrics.
Gauges report a current value that can go up or down (like memory usage, queue size). Counters always increment (like request count, orders processed). Timers measure duration of events and record both count and total time. Choose based on what you need to track: gauge for "how much now", counter for "how many total", timer for "how long does it take". Counters and timers are preferred for most application metrics as they don't require explicit value updates.
When Spring Cloud Sleuth or Spring Cloud OpenTelemetry is on the classpath, Actuator metrics automatically include tracing context (traceId, spanId) as tags via TracingMeterHandler. This allows metrics to be correlated with traces in tools like Zipkin or Jaeger. Health and info endpoints remain separate from tracing—the correlation happens at the metrics level. For Kubernetes environments, tracing context propagation ensures that spans from different pods share the same trace ID.
Never expose /actuator/env (full environment properties including secrets), /actuator/beans (lists all Spring beans and their dependencies), /actuator/configprops (configuration property names and values), /actuator/heapdump (heap dump download containing sensitive data in memory), and /actuator/threaddump (thread dumps can reveal business logic in stack traces). Always exclude these or protect them behind authentication: management.endpoints.web.exposure.exclude=env,beans,configprops,heapdump,threaddump.
Further Reading
Topic-Specific Deep Dives
-
Spring Boot Actuator in Kubernetes: Configure liveness and readiness probes for optimal Kubernetes deployment. Spring Boot 2.3+ provides built-in support for probe configuration via
management.endpoint.health.probes.enabled=true. The health endpoint adapts its response based on probe type, returning200 OKfor liveness and503 Service Unavailablewhen readiness fails. -
Micrometer Deep Dive: Micrometer is the metrics facade behind Actuator’s
/metricsendpoint. It supports multiple registries: Prometheus, Datadog, Graphite, InfluxDB, and more. Learn how to create composite meters usingCompositeMeterRegistryand howMeterBinderbeans auto-register metrics at startup. -
Prometheus & Grafana Integration: Expose Prometheus-format metrics via the
/actuator/prometheusendpoint. ConfigurePrometheusMeterRegistrywith custom collectors for business metrics. In Grafana, use the Prometheus datasource and import the JVM micorservices dashboard for instant visibility into JVM behavior under production load. -
Spring Boot Admin: A community project that provides a web UI for monitoring Spring Boot applications. It registers as a client to Actuator endpoints and aggregates health, metrics, and environment information into a centralized dashboard with alerting capabilities.
-
Health Indicator Groups: Beyond the default health aggregation, you can define custom groups with specific include lists and show-details policies. This is particularly useful when different consumers need different health perspectives: Kubernetes probes versus human operators versus external monitoring systems.
-
Customizing the Health Response: Override
HealthEndpointResponseto add additional fields, change the status code mapping, or add headers likeX-Health-Checkfor load balancer integration. TheHealthResultStatusHttpCodeConvertercontrols which HTTP status codes map to which health statuses.
External Resources
- Spring Boot Actuator Reference Documentation
- Micrometer Documentation
- Spring Boot Admin GitHub
- Prometheus Metrics Format
- Building an Observable Spring Boot Application
Conclusion
Spring Boot Actuator transforms raw application internals into actionable production intelligence. The health endpoint gives orchestrators and load balancers the signals they need, while Micrometer metrics feed observability platforms like Prometheus and Grafana. Custom health indicators let you model your application’s specific failure modes rather than relying on generic checks.
Key takeaways: expose only what you need, protect sensitive endpoints, use health groups to separate liveness from readiness probes, and always test your health indicators under failure conditions. Actuator is not a replacement for distributed tracing or structured logging, but it is the foundation that makes those tools effective in a Spring Boot environment.
Category
Related Posts
Spring Boot Build Tools: Maven & Gradle
Configure Maven and Gradle for Spring Boot projects—plugins, dependency management, packaging JARs and WARs, and build automation essentials.
Embedded Web Servers in Spring Boot: Tomcat, Jetty, Undertow
Configure embedded servers in Spring Boot: compare Tomcat, Jetty, and Undertow, tune thread pools, enable access logs, and switch implementations.
JUnit 5 & Jupiter: Lifecycle, Nested & Parameterized Tests
Explore JUnit 5 Jupiter features: master test lifecycle annotations, organize tests with @Nested, and parameterize tests with @CsvSource and @MethodSource.