Tomcat Thread Pool Options: A Production Decision Guide
Choose between Tomcat’s default connector, custom Executor, NIO, and HTTP/2 based on traffic patterns, then validate with thread dumps.
15 Sept 2025, 17:55 UTC

Decision: tune Tomcat request threads before load causes rejections
When Tomcat runs out of request-processing threads, new connections wait in a queue or are refused outright. The useful takeaway is to match the connector and thread limit to your traffic pattern, then confirm behavior with thread dumps instead of guessing at values.
This guide covers HTTP request threads. Database connection pools are a separate concern and are not interchangeable with Tomcat’s HTTP thread pool.
Supported options at a glance
| Option | Best for | Key settings | Restart required | Main risk |
|---|---|---|---|---|
| Default connector without a custom Executor | Low-to-moderate traffic, simple deployments | maxThreads, acceptCount in Connector | Yes | Thread starvation under burst |
| Custom Executor shared by Connectors | Multiple connectors, fine-grained control | maxThreads, minSpareThreads, maxQueueSize | Yes | Orphan threads on unclean shutdown |
| NIO connector | Many concurrent idle connections, keep-alive-heavy traffic | protocol="org.apache.coyote.http11.Http11NioProtocol" | Yes | Higher per-connection memory than blocking I/O |
| HTTP/2 connector (Tomcat 9 and later) | TLS-terminated sites with multiplexed browser traffic | protocol="org.apache.coyote.http2.Http2Protocol", SSL required | Yes | Higher CPU usage; not for plain HTTP |
| External connection pool such as HikariCP | Database connections, not HTTP request threads | Resource definition in context.xml | Yes | Confusing HTTP threads with database connections |
Trade-offs to weigh
Default connector without a custom Executor
Tomcat creates request threads directly in the Connector. This is simplest to configure, but all Connectors share the same defaults unless you override them. Under sudden traffic spikes, busy threads can exhaust the pool and cause connection queueing.
Custom Executor
An Executor is a named thread pool that multiple Connectors can reference. It gives you explicit control over maxThreads, minSpareThreads, and maxQueueSize. The trade-off is that changes require a server restart, and an unclean shutdown can leave threads running after Tomcat stops.
NIO connector
The NIO connector uses non-blocking I/O, which scales better for many connections that spend time idle. It is useful for long-polling or heavy keep-alive workloads, but each connection carries more memory overhead than a blocking connector, and timeout handling must be explicit.
HTTP/2 connector
Available in Tomcat 9 and later, HTTP/2 multiplexes multiple requests over one TLS connection, reducing latency for browser traffic. It requires TLS and can increase CPU usage, so it is not justified for plain HTTP or internal APIs.
External connection pool
Pools such as HikariCP manage database connections, not Tomcat request threads. Defining one in context.xml adds operational overhead and does not solve HTTP thread exhaustion.
Implementation: bind a Connector to a custom Executor
Edit $CATALINA_HOME/conf/server.xml on the host where Tomcat runs. You need write permission for the Tomcat installation directory, typically held by the tomcat system user or the account running the service.
<Executor name="productionPool" namePrefix="catalina-exec-"
maxThreads="200" minSpareThreads="25" maxQueueSize="100"
prestartminSpareThreads="true"/>
<Connector port="8080" protocol="HTTP/1.1"
executor="productionPool"
acceptCount="100"
connectionTimeout="20000"/>
Replace maxThreads with the maximum concurrent requests you want Tomcat to handle. Replace acceptCount with the number of connections Tomcat holds in the queue when all threads are busy. Replace minSpareThreads with the number of threads kept ready for sudden traffic.
Expected check: after restart, Tomcat’s startup log should show the Executor initializing without exceptions.
Validate with thread dumps
- Find the Tomcat process ID:
ps -ef | grep tomcat. Run as a user able to inspect the process. - Capture a thread dump:
jstack <pid> > /tmp/tomcat-threads.txt. Required permissions: the same user as Tomcat, or sudo with process visibility. - Inspect the output for threads named
catalina-exec-. A healthy pool shows threads in WAITING or TIMED_WAITING when idle and RUNNABLE when actively processing requests. - Compare request latency under load before and after the change using a tool such as JMeter.
Risk: setting maxThreads too high can exhaust heap memory and contribute to OutOfMemoryError. Setting it too low causes connection queueing under burst traffic.
Limitations and rollback
Changes to server.xml require a Tomcat restart, which interrupts traffic. Roll back by restoring a backup copy of server.xml and restarting again.
Practical check after rollout
Confirm the executor is active by searching the startup log: grep -i executor $CATALINA_HOME/logs/catalina.out. You should see initialization messages for the named executor and no binding errors for the Connector.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.