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.

Native with GraalVMJVM with CRaCJVM 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

You must be in control of the full of doing the training run and running your optimized application because CDS is easy to use but it’s also easy to break CDS’ assumptions.

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 custom Dockerfile
  • 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%