Startup Actuator Endpoint in Spring Boot
This post will learn more about the new Startup Actuator endpoint and how we can use it to optimize the startup time of Spring Boot applications.
Spring Boot startup sequence is a complex process. We can speed the Spring Boot application startup by tracking the application startup events. Till now we didn’t have an easier way to do this. However, Spring Boot version 2.4.0 adds support to instrument startup events.
Introduction to Startup Actuator
Before getting into this post. I want to make sure that you need to have an understanding of Spring Boot Actuators. A typical application startup contains three major parts.
- The lifecycle of the application context itself. (reading config, classes etc)
- Bean creation, evaluation and the post processing.
- Event processing from the application itself.
You can find the list of startup steps in this official spring documentation. By tracking these steps using Startup Actuator Endpoints, we can pinpoint which part of the application is dragging down the application start up time. We can even identify which beans take time and probably fix them. Spring Boot achieves this functionality via the newly introduced ApplicationStartup
implementations.
The ApplicationStartup interface comes with three implementation variants.
Class Name | Description |
---|---|
DefaultApplicationStartup | A no-op implementation that is configured by default |
BufferingApplicationStartup | An im-memory buffered implementation for capturing startup steps |
FlightRecorderApplicationStartup | An implementation that forwards captured steps as events to the java flight recorder |
Using any of the above except the default implementation would require few lines of code. We will see the examples later in this post.
We don’t have anything to speak about default implementation. Because of this implementation there just tas a placeholder. The Buffering and FlightRecorder implementations have their space. Let’s discuss each of them in detail.
Configure Startup Actuator endpoint
Remember that the application startup is made of a set of steps. When these events are fired, the Application startup can track them and provide the metric in an easily understandable format. A buffering implementation is a straightforward approach for that. This implementation is an in-memory solution that keeps a configured number of events in memory. This information is drawn out using the startup actuator endpoint or can be read and processed within the application. Here is a simple example.
@SpringBootApplication
public class ActuatorStartupExampleApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ActuatorStartupExampleApplication.class);
app.setApplicationStartup(new BufferingApplicationStartup(2048));
app.run(args);
}
}
Code language: JavaScript (javascript)
Along with the above change, you need to enable the management endpoint for the startup to see the results. To do this add the following configuration to your property file.
management.endpoints.web.exposure.include=startup
Code language: PHP (php)
If you do this right, sending a POST request to http://localhost:8080/actuator/startup will result in the event information.
Understanding the event information from the response takes a little effort, but it is not difficult. Every event entry has the following format.
{
"startupStep": {
"name": "spring.beans.instantiate",
"id": 23,
"parentId": 6,
"tags": [
{
"key": "beanName",
"value": "org.springframework.context.annotation.internalCommonAnnotationProcessor"
},
{
"key": "beanType",
"value": "interface org.springframework.beans.factory.config.BeanPostProcessor"
}
]
},
"startTime": "2020-11-14T07:28:51.245122900Z",
"endTime": "2020-11-14T07:28:51.247944100Z",
"duration": "PT0.0028212S"
}
Code language: JSON / JSON with Comments (json)
It has timing details and how the events arrange themselves in a tree structure using parentId
and id
. The tag information tells additional information about the startup step.
IMPORTANT: The Actuator Endpoint is only available for use if it is enabled along with
BufferingApplicationStartup
. Otherwise, theStartupEndpointAutoConfiguration
will not configure aStartupEndpoint
.
Collect stats with flight-recorder
In my opinion, BufferingApplicationStartup is more than enough to test things locally. But understanding these events can get a little frustrating on our own. Thus, Spring Boot provides yet another implementation for startup tracking. This implementation uses Java Flight Recorder event logging as its storage.
This ApplicationStartup
tracking implementation forwards all the event details in the form of JFR event logs. If we run our application with Flight Recorder profiling enabled, we can see the events starting to show up under the events category. Let’s test this ourself with by modifying one line in the above example.
app.setApplicationStartup(new FlightRecorderApplicationStartup());
Code language: JavaScript (javascript)
Also, note that this implementation requires at least Oracle JDK 9 or OpenJDK 8.u262. So make sure your JDK setup is right for this. Once all the above are set, you can call the application using flight recorder profiling as shown below.
$ java -XX:+FlightRecorder -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar actuator-startup-example-0.0.1-SNAPSHOT.jar
Once you run the application, there will be a recording.jfr
in your current working directory. You can use the Java Mission Control. Here is a sample screenshot.
The Event Browser can show appropriate data for the startup steps. In Event Types Tree, navigate to Spring Application > Startup Step. This would give the same information as the previous BufferingApplicationStartup
implementation.
Things to consider
Here are some important points to consider.
The startup endpoint is not for all
Using BufferingApplicationStartup
will activate a StartupEndpoint
. If you are planning to use Default or FlightRecorder implementations, then the StartupEndpointAutoConfiguration
will not activate. If you are planning to use a custom implementation then extend the BufferingApplicationStartup
.
FlightRecorderApplicationStartup and JDK version
The FlightRecorderApplicationStartup
is directly dependent on the jdk.jfr package. Oracle JDK has had this package only since java 9. Java 8 has had JFR support only since Update 262. So don’t be afraid if you are getting compile errors for jdk.jfr packages.
Filtering Events
BufferingApplicationStartup
has a fixed capacity for events and these events are stored in memory. This means a large number of events are not healthy for the application’s performance. So filtering out the events you are interested in might be a good idea. For this reason, BufferingApplicationStartup comes with an addFilter(Predicate<StartupStep> filter)
method that takes a predicate to match which steps to record.
For example, If you only need to see the events for bean creation, the setup would look like below.
@SpringBootApplication
public class ActuatorStartupExampleApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ActuatorStartupExampleApplication.class);
BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(2048);
applicationStartup.addFilter(startupStep -> startupStep.getName().startsWith("spring.beans.instantiate"));
app.setApplicationStartup(applicationStartup);
app.run(args);
}
}
Code language: JavaScript (javascript)
The above filtering would make sure that only the steps with the name spring.beans.instantiate
. The same can be confirmed by doing a POST
request to /actuator/startup
endpoint. You can further add more predicates to narrow down the list of steps. Also, you have to note that this implementation is currently available only for BufferingApplicationStartup
. FlightRecorderApplicationStartup
forwards any step data to the .JFR
files so the application is not affected in any way.
Instrumenting in production doesn’t make sense
Let’s be honest. If you are a developer working for a large corporation, you may have an instrumentation system like Dynatrace or Wily introscope. So it is not worth using another library just for startup performance data. So it is a good idea to only implement them for test environments.
Even if you choose to instrument your application, there is a problem. The implementation would require code changes to the application. Because we cannot cheat and make two builds for testing and production. In this scenario, Using a command-line argument or a system property to conditionally load the appropriate startup type would make sense. Here is my version of this implementation.
@SpringBootApplication
public class ActuatorStartupExampleApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ActuatorStartupExampleApplication.class);
String startupType = System.getProperty("app.startup.implementation.type", "");
if ("BUFFERING".equals(startupType)) {
BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(2048);
applicationStartup.addFilter(startupStep -> startupStep.getName().startsWith("spring.beans.instantiate"));
app.setApplicationStartup(applicationStartup);
} else if ("JFR".equals(startupType)) {
app.setApplicationStartup(new FlightRecorderApplicationStartup());
} else {
app.setApplicationStartup(ApplicationStartup.DEFAULT);
}
app.run(args);
}
}
Code language: JavaScript (javascript)
With the above setup, you can use the same spring boot build with different recording implementations.