Tuning Tomcat NIO: Balancing Connections and Threads for High Traffic
Stop crashing your JVM with too many threads. Learn how to tune Tomcat's NIO connector by balancing maxConnections and maxThreads for high-concurrency traffic.
11 Oct 2026, 12:59 UTC

The Connection Exhaustion Paradox
When a Tomcat server hits its limit under heavy load, the failure usually manifests in one of two ways: the server stops accepting new connections entirely, or the JVM crashes with an OutOfMemoryError due to thread exhaustion. The problem is often a misunderstanding of how the Non-blocking I/O (NIO) connector manages the relationship between a network connection and a processing thread.
The core takeaway is this: Connections are cheap; threads are expensive. In a well-tuned NIO configuration, you should maintain a high number of concurrent connections while keeping a relatively small, stable pool of worker threads.
Decoupling Connections from Processing
In older BIO (Blocking I/O) models, one thread was dedicated to one connection for its entire lifecycle. If a client was slow to send data, that thread sat idle, wasting memory. The NIO connector solves this by using a Poller. The Poller monitors multiple sockets for events (like data arriving) and only assigns a worker thread from the pool when there is actual work to be done.
This architecture introduces three critical parameters in the server.xml file:
- maxConnections: The maximum number of TCP connections the server will accept and keep open. This is the "waiting room."
- maxThreads: The maximum number of request-processing threads. This is the "active workspace."
- acceptCount: The operating system's queue for incoming connection requests when all possible connections (maxConnections) are full.
Engineering the Ratio: A Practical Example
A common mistake is setting maxThreads to a very high number (e.g., 2000) to handle more users. However, each thread consumes memory for its stack (typically 1MB) and increases CPU overhead due to context switching—the process where the CPU swaps between threads.
Consider a scenario where you expect 5,000 concurrent users, but only 200 are likely to be sending requests at the exact same millisecond. A more efficient configuration in conf/server.xml would look like this:
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
maxConnections="10000"
maxThreads="200"
acceptCount="100" />
Analysis of this configuration:
- maxConnections="10000": The server can hold 10,000 open sockets. This is ideal for Keep-Alive connections where users stay connected but aren't always active.
- maxThreads="200": Only 200 threads are available to process logic. This prevents the JVM from choking on memory and reduces CPU thrashing.
- acceptCount="100": If 10,000 connections are active and a 10,001st arrives, the OS will queue up to 100 more before rejecting them with a "Connection Refused" error.
The Risk of the "Invisible Queue"
While it is tempting to increase acceptCount to avoid connection errors, doing so often masks a deeper performance bottleneck. If the acceptCount is too high, requests sit in the OS queue for seconds before they even reach Tomcat. From the user's perspective, the site is "hanging," but your monitoring tools might show the server is technically "up." It is generally better to fail fast (reject the connection) than to let a request time out after waiting in a massive queue.
Verification and Limitations
To verify if your thread pool is saturated, you can trigger a thread dump while the server is under load using the jstack utility (run as the user owning the Tomcat process):
# Find the PID of the Tomcat process
ps -ef | grep tomcat
# Generate the thread dump
jstack <PID> > threaddump.txt
Search the output for http-nio-8080-exec-. If almost all threads are in the RUNNABLE state and the number matches your maxThreads limit, your application logic (or a downstream database) is the bottleneck, not the connector.
Limitations
The NIO connector is the standard for most Linux deployments. However, if you are running on Windows, the NIO2 connector (using org.apache.coyote.http11.Http11Nio2Protocol) may provide better performance by leveraging the OS's asynchronous I/O completion ports. Note that NIO2 behavior can vary slightly across Tomcat versions (8.5 through 10), so always verify with a load test tool like JMeter.
Closing Action
Audit your server.xml. If your maxThreads is nearly equal to your maxConnections, you are likely wasting memory and risking instability. Lower your thread count, increase your connection limit, and monitor your JVM thread dumps to find the sweet spot for your specific workload.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.