Spring Backend · Lesson 9

Spring Boot Actuator, Health, and Metrics Explained

Operate Spring Boot with minimal Actuator exposure, safe health groups, liveness and readiness boundaries, low-cardinality metrics, and graceful shutdown.

Quick answer

Spring Boot Actuator provides operational endpoints and Micrometer-backed observations, but it should expose only the information and commands a particular operator actually needs. Health is not one universal truth: liveness asks whether the process should be restarted, readiness asks whether it should receive traffic, and a detailed dependency report is a protected diagnostic view. Metrics need stable names and low-cardinality labels so an outage cannot turn telemetry into a memory or billing outage.

For the order service, expose a small health surface to the platform, restrict diagnostic endpoints to a separate management network or strong operations authorization, and keep health, info, and metrics configuration intentional. A full database or downstream-dependency failure may make readiness false, but it should not automatically make liveness false and trigger a restart storm. Graceful shutdown stops new work, lets bounded in-flight work finish, and withdraws readiness before the process disappears.

The examples target Java 17 and Spring Boot 4.1. Spring Boot 3.5 has compatible operational concepts, but endpoint defaults, health groups, property names, and chosen Micrometer registries must be checked in its reference documentation. Never promote a copied management property from a different version or deployment model without testing it behind the real proxy, service mesh, and platform probes.

Shared order-service incident stage

During the flash sale, the order service had a growing database pool wait and a rising inventory conflict rate. A dashboard showed “up” because the JVM was running, while callers were timing out and the ready replicas were unable to acquire a database connection within their budget. One attempted response proposed restarting every instance. That could have discarded useful evidence, retried more requests, and increased contention without repairing the slow query or pool exhaustion.

The safer stage of the incident is observation and traffic control. Responders compare version and configuration evidence, HTTP latency, active and pending pool connections, transaction duration, database lock wait, accepted versus rejected work, and readiness status. A readiness group can withdraw an instance when it cannot serve the dependency contract, while liveness remains focused on a process that is truly stuck or unrecoverable. The orchestration platform then avoids a dead instance without flapping all healthy-but-degraded replicas.

Health output is handled as sensitive diagnostics. A public caller may get only a coarse status, while an authenticated operator can inspect sanitized component names and timestamps. It does not return database URLs, usernames, SQL, order identifiers, secret-bearing environment variables, or exception messages. The shared incident remains connected to the transaction article: a green process is not proof that inventory reservations commit, and a red downstream check is not proof that restarting Java will repair the dependency.

Core mechanism and evidence boundary

Actuator endpoints are application interfaces, not an automatic administrator console. Bind them to a management port or network path where appropriate, expose a minimal allowlist, and apply access control distinct from ordinary customer authorization. Health, info, and Prometheus scraping often need carefully scoped access; heap dumps, thread dumps, configuration reports, shutdown controls, and loggers have much stronger disclosure or mutation risk. Do not make every endpoint web-exposed merely because it is useful during a local incident.

Health indicators aggregate selected checks into a status. A status is a coarse control signal, not a root-cause analysis. Define groups with the consumer in mind: a liveness endpoint should answer whether restarting the process is sensible, while readiness should answer whether new traffic can be served within its contract. Including an external payment provider in liveness can cause a restart loop when the provider is merely unavailable. Including a critical database dependency in readiness may be appropriate when the service cannot safely serve its main function, but choose that boundary deliberately.

Metrics describe trends and can alert before a binary health transition. Favor counters, timers, distribution summaries, gauges with clear ownership, and tags drawn from finite sets: route template, HTTP outcome family, operation type, pool name, deployment version, and a reviewed error category. Low-cardinality means each tag combination has a bounded, predictable number of values. Never tag with order ID, customer ID, email, trace ID, exception message, raw URL, SQL text, or arbitrary header. A single high-cardinality tag can produce unbounded time series and inflate heap, scrape, and storage cost.

Logs, traces, and metrics have different evidence boundaries. A metric proves an aggregate observed by this process and registry, not a complete population. A sampled trace can explain one path but cannot establish a complete or unbiased traffic distribution. A protected log event may correlate a request with an opaque ID, but it should redact credentials and personal data. Pair broad low-cardinality metrics with targeted, access-controlled diagnostic sampling.

Graceful shutdown is an availability protocol. First stop advertising readiness or accepting new traffic, then allow a bounded period for in-flight request work to finish, then close resources. It cannot guarantee every reverse proxy stopped routing immediately, undo an already committed transaction, or complete an unbounded remote call. Set server and client deadlines, drain queues where the contract permits, and make order commands idempotent so a client retry after a disconnect has an explainable outcome.

Alert rules need the same semantic care as endpoints. Alert on symptoms customers feel and on saturation that predicts them: error budget burn, sustained queue growth, pool acquisition wait, readiness loss across replicas, lock wait, and an outbox age that exceeds the service contract. A single short CPU spike or one failed health probe is usually a page-noise source rather than a recovery instruction. Every alert should identify its metric version, finite labels, owner, escalation path, and a safe first runbook step.

Operational metadata is useful only when it is trustworthy. Publish a reviewed build version, deployment timestamp, and safe configuration revision through a protected or minimal information surface. Do not publish full environment variables, Git credentials, image registry tokens, database connection strings, or arbitrary commit metadata supplied at request time. When dashboards compare before and after a release, immutable version evidence prevents responders from diagnosing the source tree while a different artifact actually handles traffic.

Minimal reproducible Spring example

The following configuration uses an intentionally small HTTP exposure surface. Network policy and identity enforcement must still be configured by the deployment; a property alone does not isolate a port. Exact property support should be verified for Spring Boot 4.1 and, separately, a Spring Boot 3.5 application.

management:
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      show-details: never
      probes:
        enabled: true
        add-additional-paths: true
  health:
    readinessstate:
      enabled: true
    livenessstate:
      enabled: true
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 20s

With management.server.port: 8081, the Actuator group URLs live on the separate management port. As written, readiness is the lifecycle readinessState group only; it does not probe PostgreSQL merely because a datasource exists. add-additional-paths: true also publishes aliases on the main application port at /livez and /readyz, which lets a platform prove that the application listener—not only the management listener—accepts connections. Configure the orchestrator to use the exact intended port and path, and test both listeners through their real network policies.

If checkout cannot safely accept any traffic without PostgreSQL, a database-aware readiness policy must be an explicit design rather than an assumption. Add a named, bounded HealthIndicator such as orderDatabase and explicitly include it with readinessState in the readiness group; give its acquisition/query a timeout shorter than the probe budget and return no connection detail. This has a shared dependency tradeoff: when every replica observes the same database outage, all replicas can withdraw together and cause an availability cliff or a probe storm. Decide whether the platform should withdraw, shed only database-dependent routes, or keep a degraded capability based on the service contract, then rehearse that failure.

An indicator should be fast, bounded, and safe. It should not issue an expensive business query or contact an unbounded remote dependency for every probe. For order-specific diagnosis, expose a protected application metric and use a time-bounded query in an operator workflow rather than adding customer identifiers to /health.

package com.example.orders;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

@Component
final class ReservationMetrics {
    private final Counter conflicts;

    ReservationMetrics(MeterRegistry registry) {
        this.conflicts = Counter.builder("orders.reservation.conflicts")
                .description("Rejected reservations due to no inventory")
                .tag("operation", "checkout")
                .register(registry);
    }

    void inventoryConflict() { conflicts.increment(); }
}

The tag has one known value. Adding orderId to this counter would create a time series for every order and turn a useful measure into a cardinality hazard. Likewise, a health detail that includes a JDBC URL, database exception, endpoint hostname, or environment property can help an attacker map internal infrastructure. Create a secure, audited diagnostic path with redaction rather than making the public probe more verbose.

Failure modes and dangerous misconceptions

“Liveness means every dependency is healthy.” It should primarily decide whether restarting this process is useful. A dependency outage often requires readiness withdrawal, traffic shaping, or dependency recovery, not a synchronized restart of every client.

“Readiness proves every user operation will succeed.” It is a coarse admission signal. A ready replica can still face an inventory conflict, a customer authorization denial, a slow query, or a partial remote failure. Pair readiness with service-level objectives and business outcome metrics.

“Expose everything only on the internal network.” Internal networks still have compromised workloads, overly broad roles, proxies, and accidental routing. Use minimal exposure, separate access control, auditing, and network restrictions together. Treat heap dumps and configuration endpoints as high-sensitivity data.

“More labels make metrics more useful.” Labels with unbounded values create unbounded series. Raw paths, UUIDs, user agents, stack traces, and exception text may look diagnostic but are usually cardinality and privacy incidents. Aggregate first; sample protected detail second.

“Graceful shutdown makes a command exactly once.” It reduces avoidable interruption, but a client can still disconnect after a commit or send a retry while a load balancer drains. Idempotency, durable state, and reconciliation decide correctness.

Security, privacy, transaction, capacity, and cost implications

Management access must be separated from customer access. Use a dedicated management identity, least privilege, firewall or service-mesh policy, and an audit trail for sensitive diagnostic actions. Do not assume an endpoint is harmless because it is read-only: configuration, environment, mappings, thread stacks, and metrics can reveal secrets, personal data, package versions, topology, or traffic patterns.

Health checks themselves consume capacity. A slow indicator multiplied by many replicas and frequent probes can add load exactly when the database is overloaded. Make checks inexpensive, cache only with an understood freshness tradeoff, use short deadlines, and avoid acquiring the same scarce resources as checkout unless that is the signal readiness truly needs. Alerting must not turn an impaired database into a probe storm.

Transaction evidence should remain local and explicit. A timer around checkout shows measured latency, not whether a database commit succeeded for every response. Correlate committed reservation outcomes, rollback counts, pool acquisition wait, lock wait, and outbox age without user-level tags. When a shutdown begins, prevent new commands before draining; do not forcibly close a transaction simply to meet a cosmetic health target.

Telemetry cost is a product constraint. Set retention and sampling budgets, cap labels through code review, and estimate series count before adding dimensions. A cheap-looking histogram across many routes, tenants, and exception messages can become expensive at scale. Observability should help responders choose a reversible mitigation, not disclose the order book or destabilize the service.

Testing and production validation

Test management exposure from the network location and identity that will use it. Assert that permitted health and scrape endpoints are reachable only as intended, that sensitive endpoints are absent or forbidden, and that details are sanitized. Verify liveness and readiness transitions using the actual framework mechanisms rather than relying only on a mocked HTTP 200.

Test metrics as a contract. Trigger a successful checkout, an inventory conflict, an authorization denial, and a validation rejection, then assert the intended bounded counters and tags exist. Add a regression test or review rule that rejects customer, order, trace, raw URL, and exception-message tags. Verify graceful shutdown with a slow but bounded request: readiness withdraws, new work is rejected or routed away according to the platform, and the in-flight request receives its documented result or retry-safe outcome.

In a production-like environment, intentionally make the database slow and observe pool wait, readiness, liveness, request shedding, and recovery. Do not regard a green liveness probe as validation of an order’s transaction semantics. Before rollout, inspect the management port through the ingress and service mesh, confirm scrape credentials and TLS, and verify that alert labels link to the deployed version and safe runbook rather than a secret diagnostic payload.

Test a probe outage from the platform’s perspective as well as from the application process. Misconfigured port names, path rewrites, authentication headers, certificate rotation, or a network policy can make a healthy application look unavailable. Conversely, an accidentally public management listener can be reachable while the intended operator network is blocked. A curl from a developer laptop is supporting evidence, not proof of the actual probe and scrape paths used by the production control plane.

Operations checklist

  • Expose only the Actuator endpoints required by a named operational consumer.
  • Separate management network and authorization from ordinary customer routes.
  • Keep detailed health and diagnostics sanitized and protected.
  • Define liveness for restart usefulness and readiness for traffic admission.
  • Make health indicators fast, bounded, and resistant to dependency probe storms.
  • Use low-cardinality metric tags with known finite values.
  • Never place identities, raw paths, SQL, secrets, or exception text in metric labels.
  • Correlate latency, pool wait, lock wait, transaction outcomes, and outbox age.
  • Withdraw readiness before a bounded graceful shutdown and preserve idempotent recovery.
  • Rehearse dependency loss, slow probes, drain behavior, and management-access rollback.

Official sources

Continue the learning path

Use Production Spring Boot Incident Troubleshooting to turn these signals into a safe recovery sequence, then test dependency behavior in Spring Boot Integration Testing with Testcontainers Explained. Continue through the Spring Backend course, Topics, and Spring Backend Engineering.

Knowledge check

Check your understanding

Answer both questions correctly to mark this lesson as mastered. You can retry without penalty.

1. The database is slow, readiness falls, and every JVM remains responsive; which health action avoids a capacity-destroying restart storm?

2. A responder proposes order IDs, exception messages, SQL, and raw URLs as metric labels; which telemetry design remains operable?