Tomcat Thread Pool Configuration in Spring Boot
Fast recall
Quick revision
Core answer
server.tomcat.accept-count (default 100); when that fills, new connections are rejected. Pool saturation manifests as latency spikes before errors because requests wait in the queue. With Java 21 + Spring Boot 3.2+, enable `spring.threads.virtual.enabled=true` to run each request on a lightweight virtual thread (Project Loom) — blocking I/O no longer occupies a platform thread, and pool tuning becomes largely irrelevant for I/O-bound workloads.Whether you know the correct property name (Boot 2.5 renamed it), understand the thread-per-request model, and know about virtual threads as the future direction
Using the old `server.tomcat.max-threads` property name; thinking increasing thread count linearly increases throughput; not mentioning virtual threads in 2026
Describe pool saturation symptoms (latency before errors), give the sizing formula for I/O-bound vs CPU-bound work, and explain how virtual threads eliminate pool tuning for blocking code
Interview-ready guide
Interview answer
The two properties that matter
server.tomcat.threads.max=200— worker pool ceiling (default 200)server.tomcat.threads.min-spare=10— warm threads kept ready- Boot 2.5 renamed from
server.tomcat.max-threads; old names still work but warn
How saturation actually manifests
- Each HTTP request occupies one worker from accept to response
- When all 200 are busy, requests queue in
accept-count(default 100) - Beyond queue depth, connections are rejected
- Latency spikes before errors — alert on
tomcat.threads.busy > 80%, don't wait for 503s
Sizing the pool
- CPU-bound work:
threads ≈ cores × 2 - I/O-bound work (most web apps):
concurrency / throughput-per-thread - Rough math: 300 ms avg × 200 threads ≈ 660 req/sec per instance
Virtual threads — the 2026 default
- Java 21 + Spring Boot 3.2+:
spring.threads.virtual.enabled=true - Each request runs on a lightweight virtual thread; blocking I/O unmounts from its carrier
- Pool tuning becomes largely irrelevant — tens of thousands of virtual threads without pressure
Two migration gotchas for virtual threads
synchronizedblocks pin virtual threads to their carrier — replace withReentrantLock- Native code also pins — monitor staging with
-Djdk.tracePinnedThreads=short
Recommendation for new projects
- Java 21 + virtual threads from day one; biggest single concurrency upgrade in decades
Tomcat Thread Pool Configuration in Spring Boot
Direct Answer
In Spring Boot 2.5+, use server.tomcat.threads.max=200 (default 200) and server.tomcat.threads.min-spare=10 (default 10, idle threads kept warm). Each HTTP request occupies one worker thread from acceptance to response completion — the classic thread-per-request model. When all threads are busy, requests queue in server.tomcat.accept-count (default 100); when that fills, new connections are rejected. Pool saturation manifests as latency spikes before errors because requests wait in the queue. With Java 21 + Spring Boot 3.2+, enable spring.threads.virtual.enabled=true to run each request on a lightweight virtual thread (Project Loom) — blocking I/O no longer occupies a platform thread, and pool tuning becomes largely irrelevant for I/O-bound workloads.
Common Mistakes & Pitfalls
Using the old server.tomcat.max-threads property name; thinking increasing thread count linearly increases throughput; not mentioning virtual threads in 2026
Check your understanding
Answer these without looking back at the article.
- What's the difference between `server.tomcat.threads.max` and `server.tomcat.max-connections`?
- How does `-Djdk.tracePinnedThreads=short` help debug virtual-thread pinning?
- Why is `ReentrantLock` preferred over `synchronized` with virtual threads?
- How do virtual threads compare to WebFlux (reactive) for handling high concurrency?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.