In a departure from earlier versions, most endpoints in the Actuator are disabled by default. As a result, only the /health and /info endpoints are accessible initially.

To enable all endpoints, we can set management.endpoints.web.exposure.include=*. Alternatively, specific endpoints can be listed for inclusion.

The Actuator now shares its security configuration with the application’s standard security rules, greatly simplifying the security model.

For projects utilizing Spring Security, adjustments to the Actuator’s security rules are necessary to permit access to its endpoints. For example, by adding the following entry for /actuator/**:

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    return http
      .authorizeExchange()
      .pathMatchers("/actuator/**").permitAll()
      .anyExchange().authenticated()
      .and().build();
}

Moreover, all Actuator endpoints are now consolidated under the default path of /actuator. This path can be customized using the new property management.endpoints.web.base-path.

Predefined Endpoints

Let’s explore the current available endpoints, noting that some have been added, others removed, and several restructured:

  • /auditevents: Lists security audit events, such as user login/logout, with filtering options available by principal or type.

  • /beans: Displays all beans within the BeanFactory, though it does not support filtering.

  • /conditions: Previously known as /autoconfig, it reports on autoconfiguration conditions.

  • /configprops: Retrieves all @ConfigurationProperties beans.

  • /env: Returns current environment properties, with the option to fetch individual properties.

  • /flyway: Offers information regarding Flyway database migrations.

  • /health: Summarizes the application’s health status.

  • /heapdump: Generates and returns a heap dump from the JVM running the application.

  • /info: Provides general information, which may include custom data, build information, or details about the latest commit.

  • /liquibase: Functions similarly to /flyway, but for Liquibase.

  • /logfile: Displays standard application logs.

  • /loggers: Allows querying and modification of the application’s logging level.

  • /metrics: Details application metrics, including both generic and custom metrics.

  • /prometheus: Returns metrics formatted for use with a Prometheus server.

  • /scheduledtasks: Provides information about scheduled tasks within the application.

  • /sessions: Lists HTTP sessions, applicable if Spring Session is in use.

  • /shutdown: Executes a graceful shutdown of the application.

  • /threaddump: Dumps thread information from the underlying JVM.

Hypermedia for Actuator Endpoints

A new discovery endpoint has been introduced, providing links to all available actuator endpoints, facilitating easier navigation.

By default, this discovery endpoint is accessible via the /actuator path.

When a GET request is sent to this URL, it will return a JSON object containing links to various actuator endpoints:

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "features-arg0": {
      "href": "http://localhost:8080/actuator/features/{arg0}",
      "templated": true
    },
    "features": {
      "href": "http://localhost:8080/actuator/features",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:8080/actuator/beans",
      "templated": false
    },
    "caches-cache": {
      "href": "http://localhost:8080/actuator/caches/{cache}",
      "templated": true
    },
    // truncated
}

This response illustrates how the /actuator endpoint lists all available actuator endpoints within the _links field.

If a custom management base path is configured, the discovery URL should reflect that base path.

For instance, setting management.endpoints.web.base-path to /mgmt requires requesting the /mgmt endpoint to view the list of links.

Notably, when the management base path is set to / (the root), the discovery endpoint is disabled to avoid conflicts with other mappings.

Health Indicators

As in previous versions, adding custom indicators is straightforward. While the abstractions for creating custom health endpoints remain unchanged, a new interface, ReactiveHealthIndicator, has been introduced to facilitate reactive health checks.

Here’s a simple example of a custom reactive health check:

@Component
public class DownstreamServiceHealthIndicator implements ReactiveHealthIndicator {

    @Override
    public Mono<Health> health() {
        return checkDownstreamServiceHealth().onErrorResume(
          ex -> Mono.just(new Health.Builder().down(ex).build())
        );
    }

    private Mono<Health> checkDownstreamServiceHealth() {
        // Use WebClient to check health reactively
        return Mono.just(new Health.Builder().up().build());
    }
}

A beneficial feature of health indicators is the ability to organize them into a hierarchical structure.

For instance, in previous example, we could classify all downstream services under a downstream-services category, which would be considered healthy as long as every nested service is reachable.

For a more in-depth exploration of health indicators, refer to article on health indicators.

Health Groups

Spring Boot now allows health indicators to be organized into groups, enabling consistent configuration across all members of a group.

For example, we can create a health group named custom by adding the following to our application.properties:

management.endpoint.health.group.custom.include=diskSpace,ping

In this configuration, the custom group will include the diskSpace and ping health indicators.

When we call the /actuator/health endpoint, the JSON response will reflect the new health group:

{"status":"UP","groups":["custom"]}

With health groups, we can view aggregated results from several health indicators.

If we query the /actuator/health/custom endpoint:

{"status":"UP"}

We can configure the group to reveal more details via application.properties:

management.endpoint.health.group.custom.show-components=always
management.endpoint.health.group.custom.show-details=always

Now, sending the same request to /actuator/health/custom will yield more comprehensive details:

{
  "status": "UP",
  "components": {
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 499963170816,
        "free": 91300069376,
        "threshold": 10485760
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}

It’s also possible to restrict the display of these details to authorized users:

management.endpoint.health.group.custom.show-components=when_authorized
management.endpoint.health.group.custom.show-details=when_authorized

We can define custom status mappings as well.

For instance, instead of returning an HTTP 200 OK response, we could configure it to return a 207 status code:

management.endpoint.health.group.custom.status.http-mapping.up=207

This tells Spring Boot to return a 207 HTTP status code if the custom group status is UP.

Metrics in Spring Boot

Spring Boot has transitioned from its in-house metrics system to Micrometer support, resulting in breaking changes. If your application previously relied on metric services like GaugeService or CounterService, these are no longer available.

Additionally, Spring Boot now provides an autoconfigured MeterRegistry bean, enabling direct interactions with Micrometer for metrics collection.

With Micrometer now included in the Actuator’s dependencies, it suffices to ensure the Actuator dependency is present in the classpath.

Moreover, the response from the /metrics endpoint has undergone significant redesign:

{
  "names": [
    "jvm.gc.pause",
    "jvm.buffer.memory.used",
    "jvm.memory.used",
    "jvm.buffer.count",
    // ...
  ]
}

The metrics list no longer displays direct values. To access specific metric values, one must navigate to the desired metric, such as /actuator/metrics/jvm.gc.pause, for a detailed response:

{
  "name": "jvm.gc.pause",
  "measurements": [
    {
      "statistic": "Count",
      "value": 3.0
    },
    {
      "statistic": "TotalTime",
      "value": 7.9E7
    },
    {
      "statistic": "Max",
      "value": 7.9E7
    }
  ],
  "availableTags": [
    {
      "tag": "cause",
      "values": [
        "Metadata GC Threshold",
        "Allocation Failure"
      ]
    },
    {
      "tag": "action",
      "values": [
        "end of minor GC",
        "end of major GC"
      ]
    }
  ]
}

Metrics are now more comprehensive, including multiple values and associated metadata.

Customizing the /info Endpoint

The /info endpoint remains largely unchanged. As before, we can add Git details by including the appropriate Maven or Gradle dependency:

<dependency>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
</dependency>

We can also incorporate build information, such as name, group, and version, using the Maven or Gradle plugin:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>build-info</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Creating a Custom Endpoint

As previously mentioned, creating custom endpoints is still feasible, but Spring Boot has restructured the process to accommodate the new technology-agnostic framework.

Here’s how to establish an Actuator endpoint for querying, enabling, and disabling feature flags in our application:

@Component
@Endpoint(id = "features")
public class FeaturesEndpoint {

    private Map<String, Feature> features = new ConcurrentHashMap<>();

    @ReadOperation
    public Map<String, Feature> features() {
        return features;
    }

    @ReadOperation
    public Feature feature(@Selector String name) {
        return features.get(name);
    }

    @WriteOperation
    public void configureFeature(@Selector String name, Feature feature) {
        features.put(name, feature);
    }

    @DeleteOperation
    public void deleteFeature(@Selector String name) {
        features.remove(name);
    }

    public static class Feature {
        private Boolean enabled;

        // [...] getters and setters
    }

}

To register the endpoint, we need to create a bean. In this example, we’re using @Component to achieve this and annotating the bean with @Endpoint.

The endpoint’s path is determined by the id parameter in @Endpoint. In our case, requests will route to /actuator/features.

Once implemented, we define operations using:

  • @ReadOperation: Maps to HTTP GET requests.

  • @WriteOperation: Maps to HTTP POST requests.

  • @DeleteOperation: Maps to HTTP DELETE requests.

Upon running the application with this endpoint defined, Spring Boot will automatically register it.

A quick way to verify this is by checking the logs:

[...].WebFluxEndpointHandlerMapping: Mapped "{[/actuator/features/{name}],
  methods=[GET],
  produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features],
  methods=[GET],
  produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features/{name}],
  methods=[POST],
  consumes=[application/vnd.spring-boot.actuator.v2+json || application/json]}"
[...].WebFluxEndpointHandlerMapping : Mapped "{[/actuator/features/{name}],
  methods=[DELETE]}"[...]

In the logs, we observe how WebFlux is exposing our new endpoint. If we switch to MVC, this will simply delegate the handling without requiring code alterations.

There are a few key considerations to keep in mind with this new approach:

  • There are no dependencies on MVC.

  • The metadata that was previously present in methods (like sensitive, enabled, etc.) is no longer available. However, we can enable or disable the endpoint using @Endpoint(id = “features”, enableByDefault = false).

  • There’s no necessity to extend a specific interface.

  • Unlike the old read/write model, we can now define DELETE operations using @DeleteOperation.

Extending Existing Endpoints

Suppose we want to ensure that our production application never runs a SNAPSHOT version. We can accomplish this by modifying the HTTP status code of the Actuator /info endpoint that returns this information. If the app happens to be a SNAPSHOT, a different HTTP status code will be returned.

We can easily extend the behavior of a predefined endpoint using the @EndpointExtension annotation or its more specific variants, @EndpointWebExtension or @EndpointJmxExtension:

@Component
@EndpointWebExtension(endpoint = InfoEndpoint.class)
public class InfoWebEndpointExtension {

    private InfoEndpoint delegate;

    // standard constructor

    @ReadOperation
    public WebEndpointResponse<Map> info() {
        Map<String, Object> info = this.delegate.info();
        Integer status = getStatus(info);
        return new WebEndpointResponse<>(info, status);
    }

    private Integer getStatus(Map<String, Object> info) {
        // return 5xx if this is a snapshot
        return 200;
    }
}

Enabling All Endpoints

To access the actuator endpoints via HTTP, both enabling and exposing them is necessary.

By default, all endpoints except for /shutdown are enabled, but only /health and /info are exposed initially.

To expose all endpoints, the following configuration should be added:

management.endpoints.web.exposure.include=*

To explicitly enable a specific endpoint (like /shutdown), we use:

management.endpoint.shutdown.enabled=true

To expose all enabled endpoints except one (for instance, /loggers), we can configure:

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=loggers