1. Overview
In this article, we introduce the Spring Boot Actuator. We’ll cover the basics first, then discuss in detail what’s available in Spring Boot.
We’ll learn how to use, configure, and extend this monitoring tool in Spring Boot and WebFlux, taking advantage of the reactive programming model.
Spring Boot Actuator has been available since April 2014, together with the first Spring Boot release.
Beginning with the release of Spring Boot 2, Actuator has been redesigned, and new exciting endpoints have been added.
We split this guide into three main sections:
2. What Is an Actuator?
In essence, Actuator brings production-ready features to our application.
Monitoring our app, gathering metrics, and understanding traffic or the state of our database becomes trivial with this dependency.
The main benefit of this library is that we can get production-grade tools without actually having to implement these features ourselves.
The actuator mainly exposes operational information about the running application — health, metrics, info, dump, env, etc. It uses HTTP endpoints or JMX beans to enable us to interact with it.
Once this dependency is on the classpath, several endpoints are available for us out of the box. As with most Spring modules, we can easily configure or extend it in many ways.
2.1. Getting Started
We need to add the spring-boot-actuator dependency to our package manager to enable the Spring Boot Actuator.
In Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Note that this remains valid regardless of the Boot version, as versions are specified in the Spring Boot Bill of Materials (BOM).
3. Spring Boot Actuator
The Spring Boot Actuator keeps its fundamental intent but simplifies its model, extends its capabilities, and incorporates better defaults.
The Actuator retains its core purpose while streamlining its design, expanding its functionality, and enhancing its default settings.
First, this version becomes technology-agnostic. It also simplifies its security model by merging it with the application one.
Among the various changes, it’s important to keep in mind that some of them are breaking. This includes HTTP requests and responses as well as Java APIs.
Lastly, the latest version now supports the CRUD model as opposed to the old read/write model.
3.1. Technology Support
The Actuator is now technology-agnostic, whereas previous versions were tied to specific frameworks and APIs. Its model is designed to be pluggable and extensible, achieving flexibility without relying on any particular framework.
Hence, with this new model, we can take advantage of MVC and WebFlux as an underlying web technology.
Moreover, forthcoming technologies could be added by implementing the right adapters.
Finally, JMX remains supported to expose endpoints without any additional code.
3.2. Important Changes
Unlike in previous versions, Actuator comes with most endpoints disabled.
Thus, the only two available by default are /health and /info.
If we want to enable all of them, we could set management.endpoints.web.exposure.include=*. Alternatively, we can list endpoints that should be enabled.
Also, Actuator now shares the security config with the regular App security rules, dramatically simplifying the security model.
So, if we are using Spring Security in our project, then we will have to tweak Actuator security rules to allow actuator endpoints.
We could just add an entry for /actuator/**:
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated()
.and().build();
}
We can find further details on the Actuator official docs.
Also, all Actuator endpoints are now placed under the /actuator path by default.**
Same as in the previous version, we can tweak this path using the new property management.endpoints.web.base-path.
3.3. Predefined Endpoints
Let’s examine the available endpoints. Some have been added, others removed, and several have been restructured:
- /auditevents lists security audit-related events such as user login/logout. Also, we can filter by principal or type among other fields.
- /beans returns all available beans in our BeanFactory. Unlike /auditevents, it doesn’t support filtering.
- /conditions, formerly known as /autoconfig, builds a report of conditions around autoconfiguration.
- /configprops allows us to fetch all @ConfigurationProperties beans.
- /env returns the current environment properties. Additionally, we can retrieve single properties.
- /flyway provides details about our Flyway database migrations.
- /health summarizes the health status of our application.
- /heapdump builds and returns a heap dump from the JVM used by our application.
- /info returns general information. It might be custom data, build information or details about the latest commit.
- /liquibase behaves like /flyway but for Liquibase.
- /logfile returns ordinary application logs.
- /loggers enables us to query and modify the logging level of our application.
- /metrics details metrics of our application. This might include generic metrics as well as custom ones.
- /prometheus returns metrics like the previous one, but formatted to work with a Prometheus server.
- /scheduledtasks provides details about every scheduled task within our application.
- /sessions lists HTTP sessions, given we are using Spring Session.
- /shutdown performs a graceful shutdown of the application.
- /threaddump dumps the thread information of the underlying JVM.
3.4. Hypermedia for Actuator Endpoints
Spring Boot adds a discovery endpoint that returns links to all available actuator endpoints. This will facilitate discovering actuator endpoints and their corresponding URLs.
By default, this discovery endpoint is accessible through the /actuator endpoint.
Therefore, if we send a GET request to this URL, it’ll return the actuator links for the various 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
}
As shown above, the /actuator endpoint reports all available actuator endpoints under the _links field.
Moreover, if we configure a custom management base path, then we should use that base path as the discovery URL.
For instance, if we set the management.endpoints.web.base-path to /mgmt, we should request the /mgmt endpoint to see the list of links.
Quite interestingly, when the management base path is set to /, the discovery endpoint is disabled to prevent the possibility of a clash with other mappings.
3.5. Health Indicators
Just like in the previous version, we can add custom indicators easily. Opposite to other APIs, the abstractions for creating custom health endpoints remain unchanged. However, a new interface, ReactiveHealthIndicator, has been added to implement reactive health checks.
Let’s have a look at a simple 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() {
// we could use WebClient to check health reactively
return Mono.just(new Health.Builder().up().build());
}
}
A handy feature of health indicators is that we can aggregate them as part of a hierarchy.
So, following the previous example, we could group all downstream services under a *downstream-*services category. This category would be healthy as long as every nested service was reachable.
Check out our article on health indicators for a more in-depth look.
3.6. Health Groups
Spring Boot now allows health indicators to be organized into groups, enabling consistent configuration to be applied across all members within a group.
For example, we can create a health group named custom by adding this to our application.properties:
management.endpoint.health.group.custom.include=diskSpace,ping
This way, the custom group contains the diskSpace and ping health indicators.
Now, if we call the /actuator/health endpoint, it will tell us about the new health group in the JSON response:
{"status":"UP","groups":["custom"]}
With health groups, we can see the aggregated results of a few health indicators.
In this case, if we send a request to /actuator/health/custom:
{"status":"UP"}
Then, we can configure the group to show more details via application.properties:
management.endpoint.health.group.custom.show-components=always
management.endpoint.health.group.custom.show-details=always
Now, if we send the same request to /actuator/health/custom, we’ll see more details:
{
"status": "UP",
"components": {
"diskSpace": {
"status": "UP",
"details": {
"total": 499963170816,
"free": 91300069376,
"threshold": 10485760
}
},
"ping": {
"status": "UP"
}
}
}
It’s also possible to show these details only for authorized users:
management.endpoint.health.group.custom.show-components=when_authorized
management.endpoint.health.group.custom.show-details=when_authorized
We can also have a custom status mapping.
For instance, instead of an HTTP 200 OK response, it can return a 207 status code:
management.endpoint.health.group.custom.status.http-mapping.up=207
We’re telling Spring Boot to return a 207 HTTP status code if the custom group status is UP.
3.7. Metrics in Spring Boot
In Spring Boot, the in-house metrics were replaced with Micrometer support, so we can expect breaking changes. If our application used metric services such as GaugeService or CounterService, they would no longer be available.
Also, Spring Boot provides an autoconfigured MeterRegistry bean, allowing us to interact directly with Micrometer for metrics.
Furthermore, Micrometer is now part of the Actuator’s dependencies, so we should be good to go as long as the Actuator dependency is in the classpath.
Moreover, the response from the /metrics endpoint has been entirely revamped:
{
"names": [
"jvm.gc.pause",
"jvm.buffer.memory.used",
"jvm.memory.used",
"jvm.buffer.count",
// ...
]
}
As we can see, the metrics list no longer includes direct values. To access specific metric values, we can now navigate to the desired metric, such as /actuator/metrics/jvm.gc.pause, to retrieve 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"
]
}
]
}
Now, metrics are much more thorough, including different values and some associated metadata.
3.8. Customizing the /info Endpoint
The /info endpoint remains unchanged. As before, we can add Git details using the respective Maven or Gradle dependency:
<dependency>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
</dependency>
Likewise, we could also include build information, including 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>
3.9. Creating a Custom Endpoint
As we pointed out previously, we can create custom endpoints. However, Spring Boot has redesigned how to achieve this to support the new technology-agnostic paradigm.
Let’s create an Actuator endpoint to query, enable, and disable 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 get the endpoint, we need a bean. In our example, we’re using @Component for this. Also, we need to decorate this bean with @Endpoint.
The path of our endpoint is determined by the id parameter of @Endpoint. In our case, it’ll route requests to /actuator/features.
Once ready, we can start defining operations using:
- @ReadOperation: It’ll map to HTTP GET.
- @WriteOperation: It’ll map to HTTP POST.
- @DeleteOperation: It’ll map to HTTP DELETE.
When we run the application with the previous endpoint in our application, Spring Boot will register it.
A quick way to verify this is to check 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 previous logs, we can see how WebFlux is exposing our new endpoint. If we switch to MVC, it’ll simply delegate that technology without having to change any code.
Also, we have a few important considerations to keep in mind with this new approach:
- There are no dependencies with MVC.
- All the metadata present as methods before (sensitive, enabled…) no longer exist. We can, however, enable or disable the endpoint using @Endpoint(id = “features”, enableByDefault = false).
- There is no longer a need to extend a specific interface.
- In contrast with the old read/write model, we can now define DELETE operations using @DeleteOperation.
3.10. Extending Existing Endpoints
Let’s imagine we want to make sure the production instance of our application is never a SNAPSHOT version.
We decided to do this by changing the HTTP status code of the Actuator /info endpoint that returns this information. If our app happened to be a SNAPSHOT, we would get a different HTTP status code.
We can easily extend the behavior of a predefined endpoint using the @EndpointExtension annotations or its more concrete specializations @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;
}
}
3.11. Enable All Endpoints
In order to access the actuator endpoints using HTTP, we need to both enable and expose them.
By default, all endpoints but /shutdown are enabled. Only the /health and /info endpoints are exposed by default.
We need to add the following configuration to expose all endpoints:
management.endpoints.web.exposure.include=*
To explicitly enable a specific endpoint (such as /shutdown), we use:
management.endpoint.shutdown.enabled=true
To expose all enabled endpoints except one (for example, /loggers), we use:
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=loggers
4. SBOMs Endpoint in Spring Boot 3
A Software Bill of Materials (SBOM) describes all the components, libraries, and dependencies used to build a software artifact. A crucial aspect of the SBOM is its role in identifying and managing security vulnerabilities in software applications.
To generate an SBOM for a Spring Boot application, we can use tools like CycloneDX, SPDX, and Syft. Spring Boot version 3.3.0 has built-in support for CycloneDX, making it a convenient choice for generating SBOMs.
Let’s start by importing the cyclonedx-maven-plugin plugin in the
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>makeAggregateBom</goal>
</goals>
</execution>
</executions>
<configuration>
<projectType>application</projectType>
<outputDirectory>${project.build.outputDirectory}/META-INF/sbom</outputDirectory>
<outputFormat>json</outputFormat>
<outputName>application.cdx</outputName>
</configuration>
</plugin>
Spring Boot automatically manages the version of this plugin through its parent.
Now, we run the mvn package command. During the Maven build, Spring Boot generates an SBOM with the help of the CycloneDX plugin and includes the SBOM in the created JAR file. If we look at the JAR’s contents, we find the SBOM in META-INF/sbom/application.cdx.json.
We can also expose the SBOM via the actuator. For that, we need to enable the SBOM actuator endpoint, which by default isn’t exposed:
management.endpoints.web.exposure.include=sbom
Now, we’re ready to run the Spring Boot application. After the startup is complete, we can query the SBOM actuator endpoint:
curl http://localhost:8080/actuator/sbom
{"ids":["application"]}
This returns a list of all available SBOMs including application, JVM, operating system, and more. By default, there’s only one SBOM named application, describing our Spring Boot application:
curl -i http://localhost:8080/actuator/sbom/application
HTTP/1.1 200
Content-Type: application/vnd.cyclonedx+json
Content-Length: 301209
{
"bomFormat" : "CycloneDX",
"specVersion" : "1.5",
"serialNumber" : "urn:uuid:3842be09-b12e-45ed-8038-babb72a53750",
"version" : 1,
...
Here, we see our application’s content. It contains information about all the dependencies of our application with their hashes and licenses, website issue tracker URLs, and more.
5. Conclusion
In this article, we talked about Spring Boot Actuator. We began by defining what Actuator means and what it does for us.
Next, we focused on Spring Boot Actuator, discussing how to use it, tweak it, and extend it. We also discussed the important security changes we can find in this new iteration. Then, we discussed some popular endpoints and how they have changed as well.
Lastly, we demonstrated how to customize and extend Actuator.
As always, the code used in this article can be found over on GitHub.