Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Spring › Spring Boot

Actuator and health checks

What does the health check answer when the connection to the payment provider is lost?

Actuator is a library that creates paths through which you can ask, from outside, about the state of a running Spring Boot application. Among them, /actuator/health answers the question "is this server healthy?". The answer is decided by combining the statuses that several components inside the application each report, and which components are asked depends on the path that you request.

Checked against real execution · Requests for 96 combinations were sent to a Spring Boot 4.1.1 server and checked against the responses

Concepts in this lab

Actuator
This module provides endpoints for checking the state of a Spring Boot application that is running in production. You can query health checks, various metrics (memory, threads, request counts, and so on), configuration information, and more, so it is connected to monitoring systems and to the health checks of load balancers.
HealthIndicator and the overall health status
A HealthIndicator is an object that checks whether one component of an application is healthy and answers with a status. Its health() method returns one of UP (healthy), DOWN (failed), OUT_OF_SERVICE (taken down on purpose), and UNKNOWN (not known), and the bean name with HealthIndicator removed becomes the name of the component. /actuator/health collects the answers of the components and takes the most severe one as the overall status, and the severity order is DOWN, OUT_OF_SERVICE, UP, UNKNOWN. When the overall status is DOWN or OUT_OF_SERVICE, the response has the HTTP status code 503, and when it is UP or UNKNOWN, it has 200. Even a single component that is DOWN makes the overall status DOWN, so decide which checks to add by asking whether the whole server should be treated as unhealthy when that check fails.
Health group
This is a named selection of only some of the components that are registered with the health endpoint. A request to /actuator/health/{group name} asks only the components in that group for their status and decides the overall status and the HTTP status code from those alone. Spring Boot 4 creates groups named liveness and readiness by default, and each contains only one component, livenessState or readinessState, which reports the state of the application itself. You decide what goes into a group by writing component names in management.endpoint.health.group.{group name}.include. A component that is not in a group is not reflected in the response of that group even when it is DOWN.
Health check (liveness, readiness)
A periodic check of whether a server is healthy. Liveness checks whether the process is alive, so that it is restarted if there is a problem. Readiness checks whether the server is ready to take requests, so that traffic is not sent to a server that is not ready. This keeps requests from reaching a server while it is starting up or shutting down.
Exposing Actuator endpoints and controlling access
These are the two conditions that must both be met before an Actuator endpoint (a feature in charge of one kind of information, such as health, env, or beans) can be used from outside. Exposure decides whether a web path is created for the endpoint, and the default of management.endpoints.web.exposure.include is health alone. Access decides whether the endpoint itself can be used, and you change it with management.endpoint.{name}.access. If either of the two is blocked, no path is created and the response is 404. For shutdown, which shuts the server down, and heapdump, which lets the whole contents of memory be downloaded, access is not permitted by default, so no path appears even when the exposure setting exposes everything.

Try these

Running with the default settings

  1. GET /actuator/health: A request without login arrives. Paths that start with /actuator are created by Actuator. Unless you configure it otherwise, health is the only one exposed over the web.
  2. /actuator/health: The path ends at health, so every registered component is asked. Three components are registered in this application: livenessState, readinessState, and pay. The first two are added by Spring Boot, and pay is the custom PayHealthIndicator.
  3. livenessState is asked: The answer is UP. This means that the application is running and has not failed. It looks only at the application itself, not at external servers.
  4. readinessState is asked: The answer is UP. This means that the application has finished starting and is ready to handle requests.
  5. PayHealthIndicator.health() runs: The answer is DOWN. This means that the payment provider server cannot be reached.
  6. Combining the statuses: The statuses collected are livenessState UP, readinessState UP, pay DOWN. The one with the highest priority among them becomes the overall status. The default priority order that Spring Boot uses (it can be changed with a setting) is DOWN, OUT_OF_SERVICE, UP, UNKNOWN. One of them is DOWN, so the overall status is DOWN.
  7. Choosing the HTTP status code: The code that matches the overall status DOWN is 503 Service Unavailable. DOWN and OUT_OF_SERVICE are answered with 503, and UP and UNKNOWN with 200. Whoever checks the health can tell whether the server is healthy from this number alone, without reading the body.
  8. Building the response body: show-details is not set, so the default, never, applies. The content of each component is hidden, and only the overall status is sent. The body is {"groups":["liveness","readiness"],"status":"DOWN"}. groups holds the names of the groups that this application has.

Result of this run

Code

// Request sent (not logged in)
// GET /actuator/health

# application.properties
# Turns off the components that are added automatically (diskSpace, ping, ssl, db). This setting keeps the lab simple
management.health.defaults.enabled=false
# show-details is not set (the default is never)

// A custom component. Removing HealthIndicator from the bean name
// payHealthIndicator gives "pay", which becomes the name of the component
@Component
class PayHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        // Returns the result of trying to connect to the payment provider server as a status. Here it is DOWN
        return Health.down().withDetail("target", "pay.example").build();
    }
}

Assumptions of this model

All 96 combinations (4 statuses of pay × 3 paths × 4 choices of show-details and requester × 2 choices of whether pay is added to the readiness group) were sent as HTTP requests to a real Spring Boot 4.1.1 server (Spring Framework 7.0.9, embedded Tomcat 11, 127.0.0.1), and the HTTP status code, the entire text of the body, the list of components that the path asks, and whether PayHealthIndicator ran were confirmed to match the screen. That the entries in the body come out in the order components, groups, status is also exactly as in the real response of this version, and the order may differ in other versions. How the components were controlled: the environment used for the check also contains H2 and the DataSource auto-configuration, so starting it with no configuration adds db, diskSpace, ping, and ssl automatically (confirmed by execution). These were turned off with management.health.defaults.enabled=false, which left only three components: livenessState, readinessState, and pay. In a real application, these components are combined into the overall status as well. Spring Security was set to permit all requests. In an application where Spring Security blocks the /actuator paths, a request without login can be rejected before it reaches the health endpoint, and that case is not covered. The "request from a logged-in user" was made not by logging in with a password but with a test filter that fills in the Spring Security authentication for the request. That leaving management.endpoint.health.roles empty shows the content to every logged-in user is consistent between the property description included in the library and the result of this run. The PayHealthIndicator on the screen is a @Component class, but in the check it was registered as a @Bean with the same bean name (payHealthIndicator), and the status that it answers was changed while the server was running. What changed from Spring Boot 3: in 4.1.1 the default of management.endpoint.health.probes.enabled is true, so the liveness and readiness groups exist even with no configuration (confirmed by execution; in Spring Boot 3 they are said to be turned on automatically only in environments such as Kubernetes, but that version was not run). HealthIndicator and Health are in the org.springframework.boot.health.contributor package of the spring-boot-health module. Cases that are not on the screen were confirmed in the same way. With no configuration, the list at /actuator contains only health, and /actuator/env, /actuator/beans, and /actuator/info return 404. Exposing everything with management.endpoints.web.exposure.include=* makes env and beans return 200, but shutdown still returns 404. This is because its access is not permitted, which is separate from exposure, and conversely, setting only management.endpoint.shutdown.access=unrestricted without exposing it also returns 404 (with both, the path appears; the POST request that actually shuts the application down was not sent). Adding management.endpoint.env.access=none to exposure.include=* makes only env return 404. Writing exposure.include=env replaces the default, so health returns 404. Turning the probes off with probes.enabled=false makes the liveness and readiness paths return 404, and groups also disappears from the body. If pay is added to the liveness group, liveness also returns 503 when pay is DOWN. The order of the statuses was confirmed with the 16 pairs of statuses of two components, and when every component is UNKNOWN, the overall status is also UNKNOWN, but the response is 200. A path that asks a single component, such as /actuator/health/pay, responds only under a setting that shows the content of each component, and returns 404 under a setting that hides it. Not verified: how livenessState and readinessState change while the application is starting or shutting down, what a load balancer or Kubernetes does on receiving this response (the lab follows the widely known explanation), management.server.port for a separate management port, the settings that change the status order and the mapping from statuses to HTTP status codes, the show-components setting, and the caching of health responses.

Previous lab: Configuration precedence · Next lab: Testing

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these