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.
If you’re interested in Java application performance, particularly in a Kubernetes environment, you can read more about it in my latest book, Hands-On Java with 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)ShellSessionIn 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"ShellSessionLeyden 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>XMLFirstly, go to the application directory and build it with Maven:
mvn clean package -DskipTestsShellSessionThen 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.

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.jarShellSessionAfter 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.jarShellSessionThe 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.

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>XMLThis repository already contains docker-compose.yml file, so you can easily run Redis on Docker with the following command:
docker compose upXMLNow 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.jarXMLIt 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