BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Spring Boot 3.2 and Spring Framework 6.1 Add Java 21, Virtual Threads, and CRaC

Spring Boot 3.2 and Spring Framework 6.1 Add Java 21, Virtual Threads, and CRaC

Key Takeaways

  • Spring Framework 6.1 and Spring Boot 3.2 support Java 21, Java’s latest LTS version, while Java 17 remains the baseline.
  • The debut of virtual threads simplifies concurrent programming and makes it much more efficient, while there were improvements in reactive programming and Kotlin coroutines.
  • Coordinated Restore at Checkpoint (CRaC) is a new way of scaling to zero startup time in the JIT JVM, while scaling to zero with the existing GraalVM Native Image support got a considerable performance boost.
  • Spring Framework 6.2 and Spring Boot 3.4, scheduled for release in November 2024, will support Jakarta EE 11 and will be aligned with Project Leyden’s premain optimizations for faster startup.
  • Spring Boot, a popular framework for new Java applications, will turn 10 in April 2024, while its basis, the Spring Framework, will turn 20 one month earlier.

Spring Framework 6.1 (released on November 16, 2023) and Spring Boot 3.2 (released on November 23, 2023) run on Java 21. These releases will make concurrent programming simpler and more efficient with virtual threads, as well as improve reactive programming and Kotlin coroutines. For "Scale to Zero" startup time reductions, there is initial support for Coordinated Restore at Checkpoint (CRaC), an OpenJDK project, while the existing GraalVM Native Image support got a considerable performance boost through a new GraalVM release. Spring Framework 6.2 and Spring Boot 3.4, scheduled for release in November 2024, will support the upcoming release of Jakarta EE 11 and will be aligned with Project Leyden’s premain optimizations.

Broadcom, the new owner of Spring, completed its purchase of VMware, the previous owner, the day before the Spring Boot 3.2 release on November 22, 2023.

Spring Framework will turn 20 in March 2024, while Spring Boot will turn 10 one month later.

New LTS Version Java 21, but Java 17 Remains the Baseline

Java 21, the current Long Term Support (LTS) version released in September 2023, is now a first-class citizen at runtime, just like Java 17–20. Java 21 finalizes virtual threads (see the next section), improves the Z garbage collector, has Record Patterns for more compact code in record type checks, and simplifies some switch statements with Pattern Matching. Oracle Java Evangelist Nicolai Parlog details the changes from Java 17 to Java 21 in his video "Upgrading from Java 17 to 21."

But Java 17, the LTS release from September 2021, remains the baseline JDK for Spring Boot 3 and Spring Framework 6. Those releases also added support for Jakarta EE 9 and GraalVM Native Image and embedded observability through Micrometer with tracing and metrics in November 2022.

It is expected that Spring Framework 6.x and Spring Boot 3.x will also support Java’s next LTS version, Java 25, which is likely to arrive in September 2025.

A New Way for Improved Efficiency: Virtual Threads

Virtual threads simplify concurrent programming in Java 21 and are a new way to improve efficiency. Instead of configuring thread pools or using callbacks, applications simply get a virtual thread and use it. Java mounts each virtual thread to a platform thread, called a carrier thread. When a virtual thread blocks for Input/Output (I/O), like database requests or HTTP calls, Java uses the carrier thread for another virtual thread.

This can dramatically increase the scalability of imperative, I/O-heavy Java applications, as they can handle even more concurrent requests. However, CPU-intensive applications will see fewer benefits as fewer opportunities exist to unmount carrier threads. The same applies to memory-intensive applications, as the memory available to the JVM will limit the number of concurrent virtual threads.

Virtual threads simplify programming because they "give us so many things we take for granted every day: sequential control flow, local variables, exception handling, single-step debugging, and profiling," as noted by Brian Goetz, Java Language Architect at Oracle.

Virtual threads lose their efficiency advantages when they hit an I/O operation or work with locks in a synchronized Java code section; the carrier thread is blocked—the so-called thread pinning. Several JDBC drivers demonstrate this behavior. Libraries in the Spring ecosystem may also pin virtual threads, as do some Java libraries.

Spring Boot 3.2 users enable virtual threads by running on Java 21 and setting the property spring.threads.virtual.enabled to true. Tomcat and Jetty will then use virtual threads, as will the applicationTaskExecutor and taskScheduler beans, the listeners for Kafka and RabbitMQ, the new HTTP RestClient, and many other parts of the Spring ecosystem.

While virtual threads deal with individual tasks, structured concurrency in Java streamlines coordinating these tasks. This preview feature in Java 21 needs more time to bake; it will likely take the unusual step of shipping its Java 21 preview again, unchanged in Java 22, for more feedback.

An Established Way for More Efficiency: Reactive Programming and Kotlin Coroutines

Reactive programming is another way to scale Java. Spring Framework has supported it since version 5 in September 2017. The reactive programming model is more complex than using virtual threads, as managing callbacks and debugging are more challenging. This may be why Goetz predicted in August 2021 that "Loom is going to kill reactive programming."

That was a controversial remark, as Project Loom "doesn’t address quite a few other features that are supported by reactive programming, namely backpressure, change propagation, composability. [...] After all, it’s just a different way of creating threads," as noted by Tomasz Nurkiewicz, Senior Engineering Tech Lead at monday.com.

Broadcom seems to agree with Nurkiewicz, as it improved support for reactive programming. For instance, it added reactive support in caching and scheduling.

Spring Framework also improved support for Kotlin Coroutines, which have simplified coordinating asynchronous tasks since 2018. Coroutines have the same goal as the upcoming structured concurrency in Java (a preview feature in Java 22). Kotlin Coroutines can use Aspect-Oriented Programming (AOP) in Spring Framework 6.1. They can also use virtual threads for higher efficiency.

A New Way to Scale to Zero: CRaC

Broadcom supports the OpenJDK Project CRaC under the "Runtime efficiency" theme. Cheaper hosting, sustainable computing, and making the JVM a good Kubernetes citizen are the benefits of this initiative. CRaC is a new way for Spring applications to "scale to zero" with sub-second startup times.

"Scale to zero" means no application instance runs unless there are incoming requests. This saves processing costs but requires near-instantaneous application startup. Java traditionally starts much too slow for such "scale to zero".

Spring Framework 6.1 and Spring Boot 3.2 deliver initial support for CRaC, which drastically reduces the startup time of a Java application and its time-to-peak performance. Users trigger a checkpoint in a running Java application. CRaC then writes a snapshot of the application to disk. That snapshot can be restored in later runs of the application.

Sébastien Deleuze, Staff Software Engineer at Broadcom and Spring Framework Core Committer, shared the results of the Spring PetClinic application with CRaC at Devoxx Belgium on October 4, 2023. On an Azure 1 CPU 2 GB RAM cloud server, startup time went from 11.97 seconds to 210 ms, which is 56 times faster. On an Azure 2 CPU 4 GB RAM cloud server, 5.76 seconds turned into 130 ms, which is 44 times faster.

Broadcom added CRaC support by mapping the taking of the checkpoint and its restore to existing Spring Bean lifecycle phases; a checkpoint maps to a complete stop of the Spring Application Context, while a restore maps to its restart. Broadcom took this opportunity to revisit the Spring Bean lifecycle model, which Spring 3.0 introduced in 2009. Components that automatically implement the lifecycle participate in CRaC checkpoints and restores. Broadcom also moved existing Spring components into the lifecycle, like task schedulers, connection pools, and other resource-holding components. Spring will support multiple, sequential stops and restarts of the Spring Application Context.

These Spring changes are needed because CRaC requires all files, sockets, and pools to close at the checkpoint and reopen after the snapshot restore. The application and all its libraries must support this, or the checkpoint will fail.

CRaC has more trade-offs besides closing and re-opening resources: First, it only works on Linux because it relies on the Linux feature Checkpoint Restore in Userspace (CRIU). This is a small trade-off in production, as most Java applications already run on Linux there. However, developers must use a Linux container for testing during development on Windows and macOS.

CRaC also requires support in the JDK. Azul currently provides such JDKs, with the free OpenJDK distribution "Azul Platform Core" and the commercial distribution "Azul Prime." Although Simon Ritter, Deputy CTO at Azul, said in June 2023 that he’s "not aware of any other distributions with plans to support CRaC at this time," Bellsoft’s Liberica OpenJDK distribution added CRaC support for Java 17 and 21 in December 2023. Goetz also discussed CRaC at Devoxx Belgium: "I think CRaC is a good exploration [...] but I’m not optimistic about it as a programming model. [...] CRaC tries to make things run faster at the expense of correctness, and I don’t like that trade-off."

With CRaC, applications must run first before taking a snapshot. Ideally, this happens in a "warmed-up state" with a production-like load. This can be in a CD/CI pipeline or in production.

Finally, application secrets, like database credentials or API keys, may leak in snapshot files. Encrypting the snapshot file solves this at the cost of a higher CPU load for writing and reading the snapshot file. Broadcom plans to add a post-restoration configuration update feature in Spring Framework. This could switch out fake database credentials in the snapshot file for the real ones after snapshot restoration through an Application Context Refresh, for instance.

Support for CRaC is "initial" because Broadcom plans further improvements, and some libraries need updates to close and reopen their resources. Broadcom has a repository with unit and application tests for Spring projects and CRaC. Its status file shows which projects pass and fail.

The Spring documentation has more information on CRaC.

An Established Way to Scale to Zero: GraalVM Native Image

Spring 6 and Spring Boot 3 already have a way to scale to zero—GraalVM Native Image, an Ahead-of-Time (AOT) compiler that creates native executables for Linux, Windows, and macOS. Like CRaC, it reduces startup time close to zero. Unlike CRaC, it also dramatically reduces memory usage, increases security, and shrinks the application file size.

On an Azure 1 CPU 2 GB RAM cloud server, the startup time of Spring PetClinic with GraalVM Native Image went from 11.97 seconds to 200 ms – 10 ms faster than CRaC. On an Azure 2 CPU 4 GB RAM cloud server, 5.76 seconds turned into 210 ms – 80 ms slower than CRaC.

Deleuze presented memory savings with GraalVM Native Image in Spring PetClinic. Memory consumption after startup was 214 MB on the JIT JVM and 204 MB with CRaC. GraalVM Community Edition reduced this to 82 MB, which is 2.6 times lower than the JVM. Oracle GraalVM (the former Enterprise Edition) achieved 61 MB, which is 3.5 times lower than the JVM.

The peak performance of a GraalVM application vs. JVM and JIT is a hotly debated topic. In the November 2, 2023 edition of "A Bootiful Podcast," Josh Long, Spring Developer Advocate at Broadcom, interviewed Thomas Wuerthinger, GraalVM founder and Oracle vice president. "The big news in the last (Oracle GraalVM) release," Wuerthinger stated, "was that for the first time, we could beat the JIT compiler configuration in all aspects, including peak throughput" (starting at 34 minutes and 56 seconds). A week later, Ritter argued on the same podcast that "the ultimate level of performance you get with GraalVM compiled code, with statically compiled code, is not going to be the same that you will get with JIT compiled code" (starting at 39 minutes and six seconds).

The GraalVM team measured performance with Spring PetClinic for release 21. They found that Oracle GraalVM achieved an 8% higher peak throughput vs. JVM with a JIT compiler—11,902 requests/second vs. 11,066.

Deleuze measured the same application. Technically, he also saw GraalVM beating the JVM, but with a 6% lead—1,363 request/s vs.1,283. However, Oracle GraalVM started with this value and degraded to 1,190 requests/s. Meanwhile, the JIT JVM performance steadily increased, overtaking Oracle GraalVM after about 33 seconds and finishing with 1,283 requests/s, still growing. Deleuze didn’t use the Performance-Guided Optimization (PGO) of Oracle GraalVM, leaving some performance on the table.

The GraalVM Community Edition started with 992 requests/s and finished with 1,008, varying only slightly. CRaC won the 60-second benchmark, starting at 896 requests/s and ending with 1,500. There is no data available beyond 60 seconds.

Just like CRaC, using GraalVM Native Image comes with trade-offs. Some Java features are impossible (like loading arbitrary classes at runtime or creating new classes or methods), and some are incomplete (like missing AWT/Java FX support on macOS or a limited amount of Java Flight Recorder events). Not all observability and testing frameworks support GraalVM, as GraalVM doesn’t allow for dynamic code generation at runtime and cannot run Java agents. Reflection and classload are possible but require configuration.

All of this can break Spring projects or third-party libraries. As with CRaC, Broadcom has a repository with tests for Spring projects and GraalVM Native Image. The community-driven GraalVM Reachability Metadata Repository has GraalVM Native Image configurations for popular libraries. When used in Spring, GraalVM Native Image automatically queries that repository.

Finally, GraalVM has a worse developer experience than JIT Java; build times are "minutes instead of seconds," as Deleuze noted in his talk, and debugging a GraalVM application on Windows and macOS requires the time-consuming compilation and running of the application in a Linux container. Java developers will mainly work with JIT JVM during development and have the CI/CD pipeline built with GraalVM Native Image. But when adding new libraries or troubleshooting production issues, they must use GraalVM Native Image on their PCs.

Faster JIT JVM Startup: Spring AOT and Class Data Sharing (CDS)

Spring AOT is the project responsible for integrating GraalVM Native Image into Spring. Some of its optimizations can be used while running on the JIT JVM without GraalVM Native Image. Broadcom estimates that this can cut startup time by about 15%. The Spring documentation has the details.

The OpenJDK JVM calculates the list of available classes and their members on each startup. The CDS feature of OpenJDK saves that information to a file and loads it at later runs. This can also cut startup time by about 15%. Spring Framework 6.1.1 supports that with a special JVM flag. Spring Boot currently does not support this but may in the future (see here and here).

Deleuze wrote a deep dive on CDS in Spring and Spring AOT. He measured the startup time reduction of the combined Spring AOT and CDS in Spring PetClinic. On an Azure 1 CPU 2 GB RAM cloud server, this combination saved 50% vs. starting from an executable JAR and 37% vs. starting from unpacked classes. On an Azure 2 CPU 4 GB RAM cloud server, the savings were 53% and 39%, respectively. He highlighted that "production deployment of Spring Boot applications should be unpacked for optimal startup time."

Other Features and Spring Next

The updated HTTP REST Client marries the fluent interface from the reactive side of Spring with the use of virtual threads:

RestClient client = RestClient.create(baseUrl);
ResponseEntity<Person> person = client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION JSON)
    .retrieve( )
    .toEntity(Person.class);

The JDBC Client API now also has a fluent interface:

JdbcClient client = JdbcClient.create(dataSource);
Optional<Person> person = client.sql("SELECT .. WHERE ID = :id")
    .param("id", 3)
    .query(Person.class)
    .optional();

Data binding and validation also improved with more consistency, such as built-in Spring MVC handler method validation or extended support for Java records.

Spring Boot 3.2 will receive free support for one year until November 23, 2024. Commercial support adds another fifteen months to that timeline until February 23, 2026. These support timeline patterns have existed since Spring Boot 2.0 in March 2018. Only Spring Boot 2.7, as the last 2.x release, got an extra six months of free and commercial support that just ended with the release of Spring Boot 3.2, while commercial support for 2.7 will remain until August 24, 2025.

Spring Boot 3.2 removes classes, methods, and properties that Spring Boot 3.0 deprecated. Spring Boot 3.2 also updated many dependencies, such as Spring Security 6.2, Kotlin 1.9, Hibernate 6.3, and Kafka 4.6. The Spring Boot 3.2 and Spring Framework 6.1 release notes give a full overview of all changes.

Spring Framework 6.2, expected in November 2024, will support Jakarta EE 11. That release includes updated APIs such as Jakarta Servlet 6.1 and Jakarta Persistence 3.2. These dependency updates are mostly invisible when using Spring’s high-level abstractions, like @RestController or Spring Data.

Spring also announced a cooperation with Project Leyden for a project with the working title "premain optimizations." These optimizations are "CDS + AOT caches on steroids," as Deleuze put it. It’s early in the process, but Broadcom expects 2-4 times faster startup time with little tradeoffs.

Interview with Sébastien Deleuze, Oliver Drotbohm, and Mark Paluch

Sébastien Deleuze, Spring Framework Core Committer at Broadcom, Oliver Drotbohm, Staff 2 Engineer at Broadcom, and Mark Paluch, Spring Data Project Lead, answered some questions from InfoQ.

InfoQ: With Java 17 and Jakarta EE 9, Spring Boot 3.0 offers a lot but requires potentially time-consuming migrations. How has its uptake been so far?

Oliver Drotbohm: Spring Boot 3 and Spring Framework 6 have been received extraordinarily well. We see enterprises upgrading to those versions both for their general infrastructure updates and their new features, such as the GraalVM support and dev services. That said, depending on the depth of the dependency on JakartaEE APIs, the package name changes can make this upgrade more challenging than previous ones. With Spring Boot Migrator and upgrade support within our tooling based on OpenRewrite, we try to help the community reduce that effort. The 2.7 generation is EOL, but luckily, Broadcom offers an extended commercial support service until August 2025.

InfoQ: Some parts of the Spring ecosystem took a while to support Spring Boot 3.0; five months in the case of Spring Cloud AWS, for instance. What’s your expectation of the uptake speed for Spring Boot 3.2?

Sébastien Deleuze: Spring Cloud AWS is a community-run project, so the Spring team can’t speak for them. But we expect a faster and smoother upgrade from Spring Boot 3.0 to the following 3.x minor releases compared to a major version upgrade. The Spring Cloud projects handled by the Spring team leveraged Spring Boot 3.2 on December 6, 2023.

InfoQ: Spring Boot 3.0 had built-in support for GraalVM Native Image. As a Spring Developer, how do I know if the libraries of my application will work with GraalVM Native Image? Not all do out of the box.

Deleuze: As a Spring Developer, you can get an overview of what parts of the ecosystem are tested and known to work with GraalVM native images on the Spring AOT smoke tests status page. We continue to polish the native support in each patch release, and we see fewer and fewer reports of compatibility issues for the Spring side of things. For open-source libraries, Spring Boot is designed to leverage GraalVM reachability metadata out of the box, and you can find the list of supported libraries here; it is growing pretty fast. Non-OSS libraries using reflection, proxies, or resources will likely require custom hints.

InfoQ: Let’s say developers find libraries that don’t work with GraalVM Native Image. Should they try fixing this by creating GraalVM Native Image configuration hints for that library?

Deleuze: Yes. For OSS libraries, the easiest path is probably to contribute those hints to the GraalVM reachability metadata repository because it provides all the guidelines and native testing infrastructure.
 
If that’s not an OSS library or if the OSS library wants to provide built-in native support, the developer can ship GraalVM reachability metadata as JSON files, potentially directly in the library JAR.
 
Spring-based libraries can also leverage more powerful and dynamic runtime hints generation (the very same capabilities used by the Spring portfolio to provide native support) following the Spring Framework-related documentation.

InfoQ: CRaC is a headline feature in Spring Boot 3.2. Taking a CRaC snapshot fails if there are open files or open sockets. As a Spring Developer, how do I know if the libraries of my application will work with CRaC?

Deleuze: We have smoke tests dedicated to CRaC support, and it is possible to get an overview of the scope where Spring provides out-of-the-box support for handling the resource lifecycle on this status page. As a Spring developer, it is possible to implement custom Spring lifecycle management as mentioned in the related Spring Framework documentation or to just use org.crac APIs to deal with application-side resource lifecycle management for CRaC.

InfoQ: Virtual threads is the second headline feature of Spring Boot 3.2. But they lose their advantages when they do I/O or work with locks in a synchronized Java code section—the so-called thread pinning. JDBC drivers are prone to this behavior. How do I know if the libraries of my application will pin virtual threads?

Mark Paluch: Many popular JDBC drivers started preparing Loom-readiness early on. Oracle and Postgres are two of the early adopters. Besides these, we know that Microsoft (SQL Server) and MySQL have undertaken efforts to make their drivers good citizens on Virtual Thread arrangements.

Otherwise, knowing how your JDBC driver will perform and whether you should expect kernel thread pinning is nearly impossible. The originating JEP444 introduced means to detect platform thread pinning by either setting the -Djdk.tracePinnedThreads=full system variable or to use Java Flight Recorder to detect platform thread pinning events.
 
During development, you can use Loom-Unit, an open-source extension to JUnit 5, to detect thread pinning.
 
Besides JDBC, the interaction with message brokers or other external systems might need scrutiny as well. If they use native libraries (JNI), they will remain on their carrier thread, potentially unnoticed. We highly recommend testing your application to investigate how it behaves on a Virtual Thread runtime so you can identify potential issues early on.

InfoQ: In your Devoxx Belgium talk on October 4, 2023, you shared PetClinic throughput numbers. Oracle GraalVM started strong and then degraded. JIT JVM started weaker but grew continuously and overtook Oracle GraalVM. How do these two options perform beyond the 60 seconds you showed on your slide?

Deleuze: I don’t have specific data points to share. However, we usually observe that Oracle GraalVM without Profile-Guided Optimization (PGO) is a bit slower (I would say a few percent) than a fully warmed-up JVM. With PGO, it’s almost the same.

InfoQ: The OpenJDK jlink tool can reduce its deployment size. Can you use it when deploying Spring Boot 3.2 applications?

Deleuze: The Spring team is interested in reducing the deployment size, but the best technical solution is not yet clear. jlink can be used for that purpose, but it comes with various constraints that may prevent wide adoption. We look forward to seeing if and how Project Leyden addresses that topic. Given the fact this is still an evolving technical landscape and that most Cloud providers support container image caching, we will likely focus on other areas of the runtime efficiency with Spring in the short term.

About the Author

Rate this Article

Adoption
Style

BT