Under The Hood
backend springboot Spring Boot 3.2+ / JDK 21+

Spring Boot 3.2+ Virtual Threads with Embedded Tomcat

Last updated
Prerequisites:
Spring Boot MVC basics
Understanding of threads and Project Loom virtual threads
  • spring
  • spring-boot
  • virtual-threads
  • tomcat
  • loom
  • threading

Read at your depth

The practical view

Spring Boot 3.2+ supports virtual threads for the web stack: set spring.threads.virtual.enabled=true and the embedded Tomcat uses virtual threads per request instead of a fixed thread pool. Requires JDK 21+. This lets the app handle far more concurrent requests without tuning max-threads (Tomcat's default 200 platform threads becomes effectively unlimited virtual threads). Virtual threads are cheap — millions can exist. Blocking code (JDBC, RestTemplate) no longer wastes a platform thread; the carrier thread is freed while the virtual thread blocks. For Spring MVC, this is the main benefit: thread-per-request with virtual threads.

The same idea in other frameworks

react equivalent
N/A — server-side Spring/Tomcat concern.

Legacy vs modern

Fixed Tomcat thread pool vs virtual threads

A fixed pool (default 200) caps concurrency and forces tuning; virtual threads lift the cap so blocking handlers no longer exhaust the pool.

before → after
Default platform thread pool
server.tomcat.threads.max=200 # ❌ OLD: bounded pool — saturation under blocking load
Virtual threads enabled
spring.threads.virtual.enabled=true # ✅ NEW: virtual-thread-per-request

Interview gotchas

Context

This is the key Loom limitation — interviewers want the pinning mechanism and its practical triggers (synchronized, native calls), because Spring code often hits it.

The mechanical answer

Pinning happens when a virtual thread blocks while holding a monitor (inside a synchronized block) or when it calls a native method that blocks. In those cases, the JVM cannot unmount the virtual thread from its carrier thread (the lock is associated with the OS thread), so the carrier thread is blocked along with the virtual thread — defeating the whole point of virtual threads for that code path. If many virtual threads pin, the small carrier pool is exhausted and throughput drops back toward platform-thread behavior. In Spring apps, pinning triggers include legacy synchronized blocks in shared code, synchronized Collections, some JDBC drivers, and native crypto calls. The mitigation: prefer ReentrantLock (JVM-supported, non-pinning) over synchronized where practical, and check for pinning with -Djdk.tracePinnedThreads=full.

Trap

The naive answer is 'virtual threads eliminate all blocking costs'. Pinning is the exception that keeps a carrier thread occupied. Another trap: assuming JDBC automatically benefits — the DB call itself blocks the virtual thread fine, but a synchronized driver or connection pool can pin. Candidates who describe the monitor-based pinning and the trace flag show real Loom understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.