Startup Actuator Endpoint in Spring Boot

In this post we will learn more about 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.

  1. The lifecycle of the application context itself. (reading config, classes etc)
  2. Bean creation, evaluation and the post processing.
  3. 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 takes 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 NameDescription
DefaultApplicationStartupno-op implementation that is configured by default
BufferingApplicationStartupAn im-memory buffered implementation for capturing startup steps
FlightRecorderApplicationStartupAn implementation that forwards captured steps as events to 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 this implementation there just tas a placeholder. The Buffering and FlightRecorder implementations have their space. Let’s discuss about 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 format that is easily understandable. The 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 then be drawn out using the startup actuator endpoint or can be read and processed within 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);
    }

}

Along with the above change, you need to enable the management endpoint for startup to see the results. To do this add the following configuration to your property file.

management.endpoints.web.exposure.include=startup

If you do this right, sending a POST request to http://localhost:8080/actuator/startup will result in event information.

Spring Boot Actuator for Startup trace information

Understanding the event information from the response take 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"
  }

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, the StartupEndpointAutoConfiguration will not configure a StartupEndpoint.

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 the 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 our with by modifying one line in the above example.

        app.setApplicationStartup(new FlightRecorderApplicationStartup());

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.

Flight recorder screen with Application startup information

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 this package only since java 9. Java 8 has 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 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);
    }

}

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 BufferingApplicationStartupFlightRecorderApplicationStartup 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, Then you may be having an instrumentation system already like Dynatrace or Wily introscope. So it is not worth using another library just for startup performance data. So it makes sense if the above implementation is only done in testing or staging servers but not on production.

But 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 these scenario, It would make sense to use a command line argument or a system property to conditionally load appropriate startup types. 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);
    }

}

With the above setup, you can make sure that same build can be used with different recording implementations.

Leave a Comment