Enterprise Java

Tracking Application Startup in Spring

Application startup performance is important for modern Spring applications. A slow startup can increase deployment time, delay container readiness, and make development and testing less efficient. In a simple Spring Boot application, startup may take only a few seconds. However, enterprise applications can contain hundreds of beans, multiple auto-configurations, database connections, messaging infrastructure, security configuration, and other initialization logic. Understanding only the total startup time is often not enough. Developers also need to understand which operations are responsible for that startup time. Spring provides the ApplicationStartup interface for observing application startup. Spring provides multiple implementations of this interface, including BufferingApplicationStartup and FlightRecorderApplicationStartup. In this article, we will build one complete Spring Boot application and use the same application to demonstrate all three concepts.

1. How Spring Application Startup Tracking Works

The Spring ApplicationContext acts as the central container of a Spring application, responsible for creating and managing Spring beans while coordinating the different stages of the application lifecycle. During startup, Spring performs a sequence of operations before the application is ready to serve requests, which can be viewed conceptually as SpringApplicationApplicationContext → Load configuration → Scan components → Register bean definitions → Create beans → Initialize beans → Start embedded server → Publish application events → Application Ready. In smaller applications, these operations usually complete quickly, but as an application grows, a particular bean, configuration class, or initialization task may introduce a noticeable startup delay. Spring’s startup tracking support helps identify such bottlenecks by representing startup operations as measurable steps that can be recorded and analyzed. Spring provides the ApplicationStartup abstraction specifically for observing these startup steps. Depending on how the startup information needs to be collected and analyzed, different implementations can be used. For example, startup events can be retained in memory for later inspection or recorded as Java Flight Recorder events for more detailed JVM-level analysis. The main components involved in Spring’s startup tracking mechanism are described below:

  • The ApplicationStartup Interface
    • ApplicationStartup is a Spring Framework interface used to instrument and track the startup process of an application.
    • It allows Spring components to record individual startup operations as StartupStep instances.
    • Each startup step can contain a name, tags, and timing information that help identify what Spring is doing during application initialization.
    • The interface provides a common abstraction, allowing different startup tracking implementations to be used without changing application code.
  • BufferingApplicationStartup
    • BufferingApplicationStartup is an implementation of ApplicationStartup that records startup events in an in-memory buffer.
    • It is useful when startup information needs to be inspected after the application has started.
    • The buffer has a configurable capacity that determines how many startup events can be retained.
    • In Spring Boot, the collected startup information can be exposed through the Actuator startup endpoint when the endpoint is enabled.
    • It is particularly useful for troubleshooting slow bean creation, configuration processing, and other startup operations.
  • FlightRecorderApplicationStartup
    • FlightRecorderApplicationStartup is an ApplicationStartup implementation that records Spring startup steps as Java Flight Recorder (JFR) events.
    • It integrates Spring startup instrumentation with the JVM’s Java Flight Recorder capabilities.
    • The resulting recording can be analyzed using JFR-compatible tools such as JDK Mission Control.
    • It is useful for correlating Spring startup activity with JVM information such as CPU usage, garbage collection, thread activity, and other runtime events.
    • This implementation is especially valuable when performing detailed startup performance analysis and investigating complex startup bottlenecks.

1.1 Choosing the Right ApplicationStartup Implementation

Use BufferingApplicationStartup when you primarily want to inspect Spring application startup steps and expose them through application-level diagnostics such as the Actuator startup endpoint, while FlightRecorderApplicationStartup is more suitable when you need deeper performance analysis and want Spring startup events to be included in a Java Flight Recorder investigation. Because both implementations implement ApplicationStartup, switching between them does not require changes throughout the application; you only need to configure the appropriate implementation based on the type of startup analysis you want to perform.

2. Implementing Startup Tracking in Spring Boot

Let’s create a Spring Boot application that includes a REST controller, a service, a deliberately slow-initializing bean, startup tracking using ApplicationStartup, support for BufferingApplicationStartup and FlightRecorderApplicationStartup, and Spring Boot Actuator startup information.

2.1 Setting Up Maven Dependencies

First, we need to configure the Maven dependencies required by the application. The application uses Spring Boot Web to expose REST endpoints and Spring Boot Actuator to provide application monitoring and startup information.

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://maven.apache.org/POM/4.0.0
        https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>startup-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

The Maven configuration uses the Spring Boot parent to manage dependency and plugin versions and configures Java 17 for the project. The spring-boot-starter-web dependency provides Spring MVC and the embedded web server required to expose REST endpoints, while spring-boot-starter-actuator provides production-ready monitoring endpoints, including the startup endpoint used later in this example. The spring-boot-starter-test dependency provides testing support, and the spring-boot-maven-plugin allows the application to be packaged and executed as a Spring Boot application.

2.2 Configuring Actuator for Startup Monitoring

Next, we configure the application and expose the required Spring Boot Actuator endpoints. Add the following properties to the application.properties file.

spring.application.name=startup-demo
management.endpoints.web.exposure.include=health,info,startup
server.port=8080

The spring.application.name property assigns the name startup-demo to the application, while server.port=8080 configures the embedded server to listen on port 8080. The management.endpoints.web.exposure.include property exposes the health, info, and startup Actuator endpoints over HTTP. The startup endpoint can provide recorded startup-step information when the application is configured with BufferingApplicationStartup.

2.3 Enabling ApplicationStartup Tracking

The main application class is where startup tracking is enabled. Instead of immediately running the application, we create a SpringApplication instance and configure its ApplicationStartup implementation before starting it.

package com.example.startup;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.metrics.BufferingApplicationStartup;
import org.springframework.core.metrics.jfr.FlightRecorderApplicationStartup;

@SpringBootApplication
public class StartupDemoApplication {

  public static void main(String[] args) {
    SpringApplication application = new SpringApplication(StartupDemoApplication.class);

    ApplicationStartup startup = new BufferingApplicationStartup(2048);
    application.setApplicationStartup(startup);
    application.run(args);
  }
}

The StartupDemoApplication class serves as the main entry point of the Spring Boot application and is annotated with @SpringBootApplication, which enables Spring Boot configuration, auto-configuration, and component scanning. A SpringApplication instance is explicitly created so that startup tracking can be configured before the application runs. The ApplicationStartup reference is initialized with BufferingApplicationStartup(2048), which keeps startup events in an in-memory buffer with a capacity of 2048 events. The setApplicationStartup() method registers this tracker with the application, allowing Spring Framework startup operations to be recorded as startup steps. Finally, application.run(args) starts the application. The imported FlightRecorderApplicationStartup represents an alternative implementation that can record startup activity as Java Flight Recorder events.

2.4 Creating the Application Service

Now, let’s create a simple Spring service that represents the application’s business layer. The REST controller created in the next section will delegate requests to this service.

package com.example.startup;

import org.springframework.stereotype.Service;

@Service
public class StartupService {
  public String getMessage() {
    return "Spring application is running";
  }
}

The StartupService class is annotated with @Service, causing Spring’s component scanning mechanism to detect the class and register it as a bean in the ApplicationContext. The getMessage() method contains a simple operation that returns the message Spring application is running. Although the service is intentionally simple, its creation and initialization occur as part of the Spring application startup lifecycle and can therefore contribute to the startup process being observed.

2.5 Exposing a REST Endpoint

Next, we expose the service through a REST endpoint. The controller uses constructor injection to obtain the StartupService bean managed by Spring.

package com.example.startup;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StartupController {

  private final StartupService startupService;

  public StartupController(StartupService startupService) {
    this.startupService = startupService;
  }

  @GetMapping("/startup")
  public String startup() {
    return startupService.getMessage();
  }
}

The StartupController class is annotated with @RestController, allowing Spring MVC to detect it and expose its handler methods as HTTP endpoints. The StartupService dependency is supplied through constructor injection and stored in the startupService field. The @GetMapping("/startup") annotation maps HTTP GET requests for /startup to the startup() method, which delegates to StartupService.getMessage() and returns the resulting message to the client.

2.6 Simulating a Slow Bean Initialization

To make startup performance analysis easier to demonstrate, we can deliberately introduce a slow bean. The following component pauses for approximately one second while its constructor executes.

package com.example.startup;

import org.springframework.stereotype.Component;

@Component
public class SlowInitializationBean {

  public SlowInitializationBean() {
    System.out.println("SlowInitializationBean initialization started");

    try {
      Thread.sleep(1000);
    } catch (InterruptedException ex) {
      Thread.currentThread().interrupt();
    }

    System.out.println("SlowInitializationBean initialization completed");
  }
}

The SlowInitializationBean class is annotated with @Component, so Spring discovers it during component scanning and creates an instance while initializing the application context. Its constructor prints a message, calls Thread.sleep(1000) to deliberately block the current thread for approximately one second, and then prints a completion message. If the thread is interrupted, the InterruptedException is handled by restoring the thread’s interrupted status with Thread.currentThread().interrupt(). This artificial delay makes it easier to demonstrate how a slow bean can increase the application’s overall startup time.

2.7 Running the Application and Analyzing Startup Output

With the application configured, we can now start it and observe the startup process. From the project directory, run the application using the Spring Boot Maven plugin.

mvn spring-boot:run

The command compiles the project and starts the Spring Boot application. During application-context initialization, Spring discovers SlowInitializationBean and invokes its constructor, causing the deliberate one-second delay. The console therefore displays the initialization messages from the slow bean along with the standard Spring Boot startup logs, making the additional startup delay visible.

Once the application has started successfully, call the REST endpoint to verify that the web layer and service are working correctly.

curl http://localhost:8080/startup

The request is handled by StartupController, which calls StartupService.getMessage() and returns the service response to the client. A successful request produces the following output.

Spring application is running

This output confirms that the embedded server is running on port 8080, the StartupController has been registered successfully, and the controller can communicate with the StartupService bean. Because the application uses BufferingApplicationStartup and exposes the Actuator startup endpoint, we can also retrieve the startup information collected while the application context was being initialized.

curl http://localhost:8080/actuator/startup

The /actuator/startup endpoint returns startup data collected by BufferingApplicationStartup. The response contains a timeline of startup steps recorded by Spring, including information associated with application-context initialization, bean creation, bean post-processing, and other framework startup operations. Each recorded step can include information such as its startup-step name, timing information, and tags that provide additional context about the operation.

{
  "springBootVersion": "3.5.0",
  "timeline": {
    "events": [
      {
        "startupStep": {
          "name": "spring.boot.application.starting"
        }
      },
      {
        "startupStep": {
          "name": "spring.context.config-classes.parse"
        }
      },
      {
        "startupStep": {
          "name": "spring.beans.instantiate"
        }
      }
    ]
  }
}

If deeper JVM-level analysis is required, replace BufferingApplicationStartup with FlightRecorderApplicationStartup in the main application class as shown below.

ApplicationStartup startup = new FlightRecorderApplicationStartup();
application.setApplicationStartup(startup);

With FlightRecorderApplicationStartup, Spring startup steps are emitted as Java Flight Recorder (JFR) events rather than being retained in the in-memory buffer used by BufferingApplicationStartup. When the JVM is started with an active flight recording, these Spring startup events can be examined together with JVM information such as CPU activity, garbage collection, thread behavior, and other runtime events using tools such as JDK Mission Control. Therefore, BufferingApplicationStartup is convenient for inspecting startup information through Spring Boot Actuator, while FlightRecorderApplicationStartup is better suited to detailed JVM-level startup performance analysis.

3. Conclusion

Spring’s ApplicationStartup interface provides an abstraction for tracking application startup steps. Instead of treating application startup as one large operation, Spring can record individual steps involved in creating and initializing the ApplicationContext. In this example, we used the same Spring Boot application with both major startup tracking implementations: BufferingApplicationStartup, which stores startup events in memory and works well with the Actuator startup endpoint, and FlightRecorderApplicationStartup, which integrates Spring startup tracking with Java Flight Recorder for deeper JVM-level performance analysis.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button