Files
spring-async-demo/virtual-threads-benchmark/docs/01-enabling-virtual-threads.md
T
asmhatre f506b01389 Add virtual-threads-benchmark: re-run Spring Boot 4.1 / JDK 25 benchmarks, JEP 491 pinning fixed
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.
2026-09-18 08:52:59 +00:00

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.