Efficient containers with spring-boot 3, java 21 and CDS
Abstract
spring-boot 3.3, java 21, java virtual threads and CDS all you to use smaller and cheaper instances with better performance.
- spring 6.1 / spring-boot 3.3: stop application context before starting Spring lifecycle ⇒ good fit for CDS.
- GraalVM vs CRaC vs CDS
Native with GraalVM | JVM with CRaC | JVM with CDS | |
---|---|---|---|
Fast startup | ✅ Instant | ✅ Instant | ✅ 1.5x faster 2x with spring AOT |
Fast warm-up | ✅ Instant | 🚧 Training run | 🚧 Training run with project leyden |
Optimal peak throughput | 🚧 Training run with Oracle GraalVM | ✅ | ✅ |
Memory consumption | ✅ Reduced | 🚧 No gain | ✅ Slightly reduced |
Compilation | ❌ Heavy and slow | ✅ Fast | ✅ Fast |
Compatibility issues | 🚧 Hints + AOT bean conditions | ❌ Complex lifecycle issues | 🚧 Training run with custom configuration |
Security | ✅ | ❌ Secret leaking in snapshots | ✅ |
Regular Java distribution | 🚧 No but not needed at runtime | ❌ No Linux only | ✅ |
- JVM with CDS: recommended for better efficiency with few constraints.
- Native with GraalVM: great option if you can deal with long build times and compatibility challenges.
Do not use java -jar app.jar
directly, extract beforehand.
Now in spring-boot 3.3, the executable JAR can self-extract:
java -Djarmode=tools -jar app.jar extract
This will extract the fat jar into:
- library jars as files for optimized class loading
- application classes + manifest file defining the classpath
# extract
java -Djarmode=tools -jar app.jar extract --destination target/app
# train run of CDS
java -XX:ArchiveClassesAtExit=target/application/application.jsa \
-Dspring.context.exit=onRefresh \
-jar target/app/app.jar
# Start app with CDS arguments
java -XX:SharedArchiveFile=target/app/app.jsa \
-jar target/app/app.jar
Buildpacks supports CDS:
- Buildpacks leverages spring-boot 3.3 self-extracting feature which is CDS friendly.
- The CDS training run is performed transparently when building the container image with the same JVM used at runtime.
Warning
Project used for optimizing a spring-boot app: https://github.com/sdeleuze/petclinic-efficient-container
- upgrade java version from 8 to 21
- upgrade spring-boot to latest
- use virtual threads
spring.threads.virtual.enabled=true
- use
./mvnw -DskipTests spring-boot:build-image
instead of using customDockerfile
- enable spring AOT optimization
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>process-aot</id>
<goals><goal>process-aot</goal></goals>
</execution>
</executions>
<configuration>
<image>
<env>
<BP_SPRING_AOT_ENABLED>true</BP_SPRING_AOT_ENABLED>
</env>
</image>
</configuration>
</plugin>
Warning
There are some considerations before using this optimization, e.g. you cannot change your datasource from postgres to h2.
To enable CDS:
- add env variable when building the image:
<env>
<BP_SPRING_AOT_ENABLED>true</BP_SPRING_AOT_ENABLED>
<BP_JVM_CDS_ENABLED>true</BP_JVM_CDS_ENABLED>
</env>
- main constraint:
spring.data.jdbc.dialect=postgresql
You can also improve webjars:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-lite</artifactId>
<version>1.0.0</version>
</dependency>
Using virtual threads with smaller machine makes JDBC template throughput way bigger:
- 1CPU/512M: +1392%
- 1CPU/1024M: +1370%
- 2CPU/1024M: +225%