Spring Boot Actuator
Actuator is Spring Boot's dashboard—providing health status, metrics, and configuration information—and offers operations teams a "bird's-eye view."
1. What You'll Learn
- Actuator Endpoint Overview:
/health//info//metrics//env//beans - Endpoint Enablement and Exposure Policy:
management.endpoints.web.exposure.include - Custom HealthIndicator to monitor database connectivity with external services
@ReadOperation/@WriteOperationCustom Endpoints- Security Hardening: Endpoint Access Control and Network Segmentation
2. A True Story of an Operations Engineer
(1) Pain Point: The production environment is like a black box
Bob is responsible for the operations and maintenance of the OrderFlow production environment, but the application is a complete black box to him: Are the database connections working properly? How much memory is being used? Which APIs are responding slowly? Every time a problem arises, he has to ask Alice to add logs or restart the application to troubleshoot it, resulting in an average Mean Time to Recovery (MTTR) of over 1 hour. Charlie has demanded that the MTTR be reduced to 10 minutes.
(2) Solution for the Actuator
Actuator is ready to use right out of the box and offers a wide range of operations and maintenance endpoints:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
curl http://localhost:8080/actuator/health
# {"status":"UP","components":{"db":{"status":"UP"},"diskSpace":{"status":"UP"}}}
(3) Revenue
After Bob started using Actuator, /health monitored database connectivity, /metrics tracked API response times, and a custom HealthIndicator checked the external payment gateway, reducing the MTTR from 1 hour to 5 minutes.
3. Overview of Actuator Endpoints
(1) List of Built-in Endpoints
| Endpoint | Description | Default Exposure |
|---|---|---|
/actuator/health |
App Health Status | ✅ Yes |
/actuator/info |
App Information | ✅ Yes |
/actuator/metrics |
List of Indicators | ❌ No |
/actuator/metrics/{name} |
Specific metric value | ❌ No |
/actuator/env |
Environment Configuration | ❌ No |
/actuator/beans |
Bean List | ❌ No |
/actuator/loggers |
Log Level | ❌ No |
/actuator/threaddump |
Thread Dump | ❌ No |
/actuator/heapdump |
Heap Dump | ❌ No |
/actuator/prometheus |
Prometheus-formatted metrics | ❌ No |
(1) ▶ Example: Enabling the Actuator dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Output:
// Execution successful
(2) ▶ Example: Configuring Endpoint Exposure
management:
endpoints:
web:
exposure:
include: health,info,metrics,env,loggers
base-path: /actuator
endpoint:
health:
show-details: when-authorized
metrics:
enabled: true
info:
env:
enabled: true
Output:
The configuration has taken effect.
| Exposure Policy | Configuration Value | Description |
|---|---|---|
| Health and Information Only | include: health,info |
Safest, Default |
| On-Demand Exposure | include: health,info,metrics |
Recommended |
| Show All | include: "*" |
Development Environment Only |
| Exclude Specific | exclude: env,beans |
Exclude from All |
4. Detailed Explanation of the Health Endpoint
(1) Health Status Aggregation Rules
graph TD
A["Health Status<br/>Aggregator"] --> B["DiskSpace<br/>UP"]
A --> C["DataSource<br/>UP"]
A --> D["PaymentGateway<br/>DOWN"]
A --> E["Redis<br/>UP"]
D --> F["Overall: DOWN<br/>Any DOWN → DOWN"]
| Status | Meaning | Aggregation Rules |
|---|---|---|
| UP | Normal | — |
| DOWN | Exception | Any single DOWN → Overall DOWN |
| DEGRADED | Degraded | Non-critical component degradation |
| OUT_OF_SERVICE | Service Not Available | Any one → All OUT_OF_SERVICE |
| UNKNOWN | Unknown | Default |
(1) ▶ Example: Custom HealthIndicator
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
private final RestTemplate restTemplate;
public PaymentGatewayHealthIndicator(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public Health health() {
try {
ResponseEntity<String> response = restTemplate.getForEntity(
"https://api.payment-gateway.com/ping", String.class);
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("gateway", "reachable")
.withDetail("responseTime", response.getHeaders().getDate())
.build();
}
return Health.down()
.withDetail("gateway", "unhealthy")
.withDetail("statusCode", response.getStatusCode())
.build();
} catch (Exception e) {
return Health.down()
.withDetail("gateway", "unreachable")
.withDetail("error", e.getMessage())
.build();
}
}
}
Output:
// Execution successful
{
"status": "DOWN",
"components": {
"diskSpace": {"status": "UP", "details": {"total": 536870912000, "free": 268435456000}},
"db": {"status": "UP", "details": {"database": "MySQL", "validationQuery": "SELECT 1"}},
"paymentGateway": {"status": "DOWN", "details": {"gateway": "unreachable", "error": "Connection refused"}}
}
}
5. Detailed Explanation of Metrics Endpoints
(1) ▶ Example: View HTTP request metrics
# List all available metrics
curl http://localhost:8080/actuator/metrics
# Get specific metric: HTTP server requests
curl http://localhost:8080/actuator/metrics/http.server.requests
# Get filtered metric
curl "http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/api/v1/orders&tag=status:200"
Output:
{"status":"ok","data":{}}
| Common Metrics | Description |
|---|---|
http.server.requests |
Distribution of HTTP Request Durations |
jvm.memory.used |
JVM Memory Usage |
jvm.threads.live |
Number of Active Threads |
process.cpu.usage |
Process CPU Usage |
disk.total / disk.free |
Disk Space |
hikaricp.connections.active |
Database Connection Pool |
(2) ▶ Example: Custom Business Metrics
@Service
public class OrderMetrics {
private final Counter orderCreatedCounter;
private final Timer orderCreationTimer;
private final AtomicLong pendingOrders;
public OrderMetrics(MeterRegistry registry) {
this.orderCreatedCounter = Counter.builder("orderflow.orders.created")
.description("Total orders created")
.tag("service", "orderflow")
.register(registry);
this.orderCreationTimer = Timer.builder("orderflow.orders.creation.time")
.description("Order creation time")
.register(registry);
this.pendingOrders = registry.gauge("orderflow.orders.pending",
new AtomicLong(0));
}
public void recordOrderCreated() {
orderCreatedCounter.increment();
pendingOrders.incrementAndGet();
}
public void recordOrderCompleted() {
pendingOrders.decrementAndGet();
}
public Timer getCreationTimer() {
return orderCreationTimer;
}
}
Output:
// Execution successful
6. Custom Endpoints
(1) ▶ Example: Custom OrderStats Endpoint
@Endpoint(id = "orderstats")
@Component
public class OrderStatsEndpoint {
private final OrderRepository orderRepository;
public OrderStatsEndpoint(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@ReadOperation
public Map<String, Object> orderStats() {
long total = orderRepository.count();
long pending = orderRepository.countByStatus("PENDING");
long completed = orderRepository.countByStatus("COMPLETED");
long cancelled = orderRepository.countByStatus("CANCELLED");
return Map.of(
"totalOrders", total,
"pendingOrders", pending,
"completedOrders", completed,
"cancelledOrders", cancelled,
"completionRate", total > 0
? String.format("%.2f%%", (double) completed / total * 100)
: "N/A"
);
}
@ReadOperation
public Map<String, Object> orderStatsByStatus(
@Selector String status) {
long count = orderRepository.countByStatus(status.toUpperCase());
return Map.of("status", status, "count", count);
}
}
Output:
// Execution successful
management:
endpoint:
orderstats:
enabled: true
endpoints:
web:
exposure:
include: health,info,orderstats
curl http://localhost:8080/actuator/orderstats
# {"totalOrders":1523,"pendingOrders":45,"completedOrders":1420,"cancelledOrders":58,"completionRate":"93.30%"}
curl http://localhost:8080/actuator/orderstats/PENDING
# {"status":"PENDING","count":45}
7. Security Hardening
(1) Endpoint Security Policy
| Level | Policy | Description |
|---|---|---|
| Network Layer | Dedicated Management Port | management.server.port=8081 |
| Network Layer | Bind to Internal IP | management.server.address=127.0.0.1 |
| Application Layer | Spring Security Control | .requestMatchers("/actuator/**").hasRole("ADMIN") |
| Endpoint Layer | Disable Sensitive Endpoints | management.endpoint.env.enabled=false |
(1) ▶ Example: Production-Level Security Configuration
# application-prod.yml
management:
server:
port: 8081 # Separate management port
address: 127.0.0.1 # Bind to localhost only
endpoints:
web:
exposure:
include: health,prometheus,orderstats
base-path: /actuator
endpoint:
health:
show-details: when-authorized
env:
enabled: false
beans:
enabled: false
heapdump:
enabled: false
Output:
Monitoring config loaded
Prometheus targets: 3 active
Grafana dashboard: ready
| Environment | Exposure Policy | Management Port |
|---|---|---|
| Development | include: "*" |
Port 8080 |
| Test | include: health,info,metrics,env |
Same port 8080 |
| Production | include: health,prometheus |
Dedicated Port 8081 + Internal Network |
8. Comprehensive Example: Complete Configuration of the OrderFlow Actuator
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,orderstats
endpoint:
health:
show-details: when-authorized
orderstats:
enabled: true
metrics:
tags:
application: ${spring.application.name}
info:
env:
enabled: true
info:
app:
name: OrderFlow Service
version: @project.version@
description: E-commerce order management microservice
// PaymentGatewayHealthIndicator.java
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
private final RestTemplate restTemplate;
public PaymentGatewayHealthIndicator(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public Health health() {
try {
ResponseEntity<String> resp = restTemplate
.getForEntity("https://api.payment.com/ping", String.class);
return resp.getStatusCode().is2xxSuccessful()
? Health.up().withDetail("gateway", "reachable").build()
: Health.down().withDetail("statusCode", resp.getStatusCode()).build();
} catch (Exception e) {
return Health.down().withDetail("error", e.getMessage()).build();
}
}
}
// OrderStatsEndpoint.java
@Endpoint(id = "orderstats")
@Component
public class OrderStatsEndpoint {
private final OrderRepository orderRepository;
public OrderStatsEndpoint(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@ReadOperation
public Map<String, Object> stats() {
return Map.of(
"total", orderRepository.count(),
"pending", orderRepository.countByStatus("PENDING"),
"completed", orderRepository.countByStatus("COMPLETED")
);
}
}
❓ FAQ
/health is for load balancers and K8s probes; it does not expose sensitive details. /health with details is intended for operations teams and requires ADMIN permissions. Use show-details: when-authorized in production environments.HealthAggregator or use the StatusAggregator bean. The default rule is: Any DOWN → Overall DOWN. You can customize the priority using statusOrder./actuator/env expose passwords?password, secret, key, and token (displaying •••••• instead). It is still recommended to disable this endpoint in production environments.micrometer-registry-prometheus dependency, expose the prometheus endpoint, and add a collection target in your Prometheus configuration. This will be covered in detail in a later lesson (L23).📖 Summary
- Actuator provides operational endpoints such as health, metrics, configuration, and threads; by default, only health and info are exposed.
- Custom HealthIndicator to monitor connectivity to external services
- The Metrics endpoint provides built-in metrics for HTTP, JVM, connection pools, and more
- Custom endpoints use
@Endpoint+@ReadOperation/@WriteOperation - Production Security: Dedicated management port + Internal network binding + Limited exposure + Spring Security
show-details: when-authorizedControl the display of health details
📝 Exercises
-
Basic Exercise (Difficulty ⭐): Add an Actuator to OrderFlow, configure it to expose the health, info, and metrics endpoints, and verify that
/actuator/healthreturns the "UP" status. -
Advanced Exercise (Difficulty: ⭐⭐): Implement a custom
PaymentGatewayHealthIndicatorandOrderStatsEndpoint, and configure a separate management port for the production environment. -
Challenge (Difficulty: ⭐⭐⭐): Add the
micrometer-registry-prometheusdependency, expose the/actuator/prometheusendpoint, write custom business metrics (order creation count, order placement time), and consider naming conventions and tag design for the metrics.



