Companion module for the rewritten post 'Virtual Threads on Spring Boot 4.1: The Benchmarks, Re-Run, and the Pinning Advice That Expired', retitled and re-benchmarked on Boot 4.1.1 / JDK 25.0.4.1 (the original post was written against Boot 3.4 / JDK 21). Covers: I/O-bound and CPU-bound throughput (platform vs virtual threads, including a JIT-warmup benchmarking bug this build caught and fixed), JEP 491 proof that synchronized no longer pins a virtual thread's carrier across a blocking call as of JDK 24 (obsoleting the old avoid-synchronized advice), proof that -Djdk.tracePinnedThreads=full is inert on JDK 25, and JEP 506's finalized ScopedValue API (JDK 25 GA, no --enable-preview, and a different shape than the old preview API). Kept as its own module rather than a new top-level repository, alongside the existing async/ module, which already has a stronger dual-JDK JEP 491 proof that this module's docs cross-link to instead of duplicating.
2.4 KiB
1. Enabling virtual threads on Spring Boot 4.1
README | Next: 02-benchmark-methodology.md
Source: application.yml, DemoController.java.
Test: ThreadTypeTest.java.
Transcript: docs/output/00-thread-type-confirmation.txt.
Still one property
spring.threads.virtual.enabled=true is unchanged since Boot 3.2 and still the whole
configuration surface for most apps on Boot 4.1. It requires JDK 21+; this repo runs it on
JDK 25.
What actually flips
Confirmed against a real running embedded server, not by reading the reference docs:
enabled=false -> Thread: Thread[#9711,http-nio-auto-5-exec-1,5,main] | Virtual: false
enabled=true -> Thread: VirtualThread[#9737,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-2 | Virtual: true
The platform-thread name prefix changed between the Boot 3 era and this repo's Boot 4.1 run
(http-nio-auto-5-exec-1 here vs. the http-nio-8080-exec-3 shape in the older post) because
this test binds to server.port=0 (a random port) rather than a fixed 8080 -- Tomcat's
connector-name-derived thread prefix reflects that. Cosmetic, but worth not being surprised by
if your own logs look different from either example.
Test infrastructure trap worth knowing about
Setting the property via SpringApplicationBuilder.properties(...) and expecting it to win
over application.yml is a real bug this repo hit while building the benchmark below:
.properties(...) binds to Spring's defaultProperties source, which has the lowest
precedence of any property source Spring Boot recognizes -- lower than application.yml,
which had spring.threads.virtual.enabled: false as its own explicit default. The fix was
passing it as a command-line-style argument to run(...) instead
("--spring.threads.virtual.enabled=" + value), which binds at the highest precedence. See
LoadBenchmarkTest.java for
where this was caught -- the first draft of the CPU-bound benchmark below silently ran both
scenarios on platform threads and reported a difference that didn't exist.
Next: 02-benchmark-methodology.md.