7 min read

Startup Actuator Endpoint | Collect application startup stats with ease

November 14, 2020

Spring Boot’s startup sequence is a complex process. Tracking the application startup can help us understand how the beans are created and which beans take time etc. These metrics can be used to understand our application lifecycle in a better way. For this reason Spring Boot introduced new feature part of 2.4.0 release. In this post we will figure out what this feature is about and how it can/should be used.

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.

The list of startup steps and their related information can be found in the official spring documentation. If we could gather information on these steps it will give an idea of where the startup time is being spent the most. 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 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 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.

In this post, I’m not going to speak about the default implementation as its purpose is to be a placeholder when no tracking is needed. The Buffering and FlightRecorder implementations have their space. Let’s discuss about each of them in detail.

BufferingApplicationStartup

Remember that the application startup can be split into 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 all of this is done correctly, then sending a POST request to http://localhost:8080/actuator/startup would yield a JSON response with event information.

JSON response from startup actuator endpoint

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.

FlightRecorderApplicationStartup

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.

Profiled data on Java Mission Control

The appropriate data for this can be found under the Event Browser. 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 of the 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. For OpenJDK 8, the JFR support has been back-ported since Update 262 or later. Spring Boot’s official documentation suggests to use OpenJDK U 262 or above

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

But there is a problem. The implementation involves code change to the application thus we cannot cheat and make two builds for testing and production. In these scenario, It would make sense to use a commandline 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.

About the author
Raja Anbazhagan

I'm a Software Engineer with over 7 years of experience in working with Enterprise Java applications. Lately, I am focused on cloud-based Java applications and serverless technologies. I spend most of my spare time on stackoverflow and this blog.