Speed up Java Startup with Spring Boot and Project Leyden

Speed up Java Startup with Spring Boot and Project Leyden

This article explains how to use the new Java features provided within Project Leyden to speed up Spring Boot application startup. Project Leyden is an OpenJDK initiative focused on making Java applications start faster, warm up quicker, and use less memory. It builds on CDS (Class Data Sharing) and Ahead-of-Time optimizations to move selected computations out of runtime and perform them earlier, reducing work during application startup and execution. It is currently in the experimental phase. So, it is not a part of the standard JDK distribution, and you should definitely not try it in production. To use it, you need the so-called early access build of the JDK, which is available here. I downloaded the latest version, which is based on an incomplete JDK 26 build.

By the way, there are two early access builds of Project Leyden right now: EA1 and EA2. In this article, I’m referring to the early access 2. You can compare both versions in the project’s release notes. Here are the release notes for EA1. On the other hand, here are the release notes for EA2. There are significant differences between the two versions, for example, in the JVM flags used.

Other Articles

On my blog, you’ll find several other articles that discuss ways to optimize Java application startup times. For example, the following article explains how to use the CRaC approach to reduce the startup time of Java applications on Kubernetes. This post, on the other hand, shows how to temporarily increase the CPU resources available to our application just while it’s starting up on Kubernetes.

Source Code

Feel free to use my source code if you’d like to try it out yourself. To do that, you must clone my sample GitHub repositories used in this article. You can find them here: repository1 (Spring Boot, H2, Liquibase) and repository2 (Spring Boot, Redis). Then you should only follow my instructions.

Prerequisites

Before we begin, we need to install the JDK version provided by the Leyden project. I installed the macOS version available here. If you need a version for a different operating system, you can find it at the link I included in the introduction to this article. I assume you’re installing this version of Java solely to test Leyden’s functionality. So just extract it somewhere, and during the session, set the JAVA_HOME environment variable to its location. Here’s how it looks for me:

$ export JAVA_HOME=/opt/addons/jdk/Contents/Home
$ java --version
openjdk 26-leydenpremain 2026-03-17
OpenJDK Runtime Environment (build 26-leydenpremain+1-24)
OpenJDK 64-Bit Server VM (build 26-leydenpremain+1-24, mixed mode, sharing)
ShellSession

In the same window, let’s also check the version of Maven. It should also see the 26-leydenpremain version of Java.

$ mvn --version
Apache Maven 3.9.12 (848fbb4bf2d427b72bdb2471c22fced7ebd9a7a1)
Maven home: /opt/homebrew/Cellar/maven/3.9.12/libexec
Java version: 26-leydenpremain, vendor: Oracle Corporation, runtime: /opt/addons/jdk/Contents/Home
Default locale: en_PL, platform encoding: UTF-8
OS name: "mac os x", version: "15.7.4", arch: "aarch64", family: "mac"
ShellSession

Leyden with Spring Boot Application

Spring Boot with an in-memory database and Liquibase

Let’s begin with the spring-boot-tips repository. This application starts up with the embedded, in-memory H2 database and connects to it immediately after launching. Then it uses Liquibase to initialize the database schema. There are also some additional dependencies, such as Spring Data JPA or Springdoc OpenAPI.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</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-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-liquibase</artifactId>
</dependency>
<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  <version>3.0.2</version>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>
XML

Firstly, go to the application directory and build it with Maven:

mvn clean package -DskipTests
ShellSession

Then you can start it with Java in the standard way just to check the app’s startup time. You can see my startup time below. It takes 3 seconds to start the app and initialize an in-memory database with Liquibase.

leyden-spring-boot-aot-cache

Now, let’s start the same application with the -XX:AOTCacheOutput flag. This flag tells the JVM to generate and save an Ahead-Of-Time (AOT) compilation cache to a file during execution. It should contain the desired name of the output file.

java -XX:AOTCacheOutput=spring-boot-tips.aot -jar target/spring-boot-tips-1.3-SNAPSHOT.jar
ShellSession

After you stop the app, its AOT cache should be written to a file. You should see something similar to what I see in the logs.

Next, to run the application using the AOT cache previously saved in a file, use the -XX:AOTCache parameter specifying the file’s location on disk.

java -XX:AOTCache=spring-boot-tips.aot -jar target/spring-boot-tips-1.3-SNAPSHOT.jar
ShellSession

The app launches about a second faster than before. This example may not be entirely representative, since an in-memory database is also being launched here.

leyden-spring-boot-aot-cache-start

Spring Boot with an external Redis database

Now let’s move on to the sample-spring-redis repository. This application no longer uses the in-memory database or initializes it at startup. Instead, it connects to a running Redis instance. Here’s a list of included Maven dependencies.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
XML

This repository already contains docker-compose.yml file, so you can easily run Redis on Docker with the following command:

docker compose up
XML

Now you can repeat the exact same steps as for the previous app. Start the app with the –XX:AOTCacheOutput JVM parameter.

java -XX:AOTCacheOutput=sample-spring-redis.aot -jar target/sample-spring-redis-1.1-SNAPSHOT.jar
XML

It took 1.1 seconds on my laptop to start the sample-spring-redis app. After you stop the app, the AOT cache is saved to the sample-spring-redis.aot file.

Here’s the final comparison. Sample Spring Boot app starts 0.65 seconds with Project Leyden, which is 0.45 seconds earlier than before (1.1 seconds). Not bad 🙂

Conclusion

My tests with Project Leyden show about a 40% faster startup for the Spring Boot app, which is a solid improvement but not exactly groundbreaking. Similar benefits have already been achieved with solutions like CRaC, for example, in Azul’s JVM. Of course, the benefit of Project Leyden is that it can be included as a standard in the OpenJDK distribution. However, in practice, the biggest gains for startups still come from GraalVM and its native-image approach.

Leave a Reply