Tomcat NIO Connector: Balancing maxThreads, maxConnections and acceptCount Under Load
Tomcat NIO decouples connections from worker threads. Tuning maxThreads, maxConnections and acceptCount together avoids thread exhaustion and hidden queuing under load.
08 Oct 2025, 00:54 UTC

Traffic spikes on Tomcat often look like a thread problem, but with the NIO connector the bottleneck is usually the relationship between connections and worker threads, not raw thread count. The useful takeaway is to treat maxThreads as processing capacity, maxConnections as connection capacity, and acceptCount as a short overflow buffer, and tune them together rather than raising maxThreads alone.
Why NIO changes the connection-to-thread math
The NIO connector uses Java NIO selectors. One or a few selector threads accept new TCP connections and read/write data without blocking, and hand completed requests to a worker thread pool for application processing. This decouples the number of open sockets from the number of threads doing work.
That separation is helpful for keep-alive traffic and slow clients, but it also means you can have thousands of idle connections while the worker pool is saturated. Monitoring only active threads hides that condition.
Three knobs that interact
maxThreads defines the maximum number of request processing threads in the executor. When all threads are busy, new requests wait.
maxConnections caps the total number of concurrent TCP connections Tomcat will accept, including idle keep-alive connections. It limits memory use from socket state even if threads are available.
acceptCount controls the operating system TCP backlog queue length for new connections when all worker threads are busy. Requests in this queue are not yet accepted by Tomcat.
Tuning these together matters for throughput versus memory. A high maxConnections with a low maxThreads allows many idle connections but little processing. A high maxThreads with a low maxConnections rejects connections early.
Worked example configuration
Review server.xml under the Connector element. The following is an example configuration for an HTTP NIO connector on Tomcat 9 or 10. Adjust values for your workload and verify in a non-production environment.
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="200"
maxConnections="1000"
acceptCount="100"
connectionTimeout="20000"
enableLookups="false" />In this example, up to 200 requests can be processed concurrently. Up to 1000 total connections can be held, including keep-alive. When all 200 threads are busy, up to 100 additional connection attempts can queue in the OS accept queue before clients receive a connection refused.
Configuration behavior can vary slightly between Tomcat 9 and Tomcat 10 due to the Jakarta EE namespace migration. Confirm attribute names in the version you run by inspecting the Connector documentation for that release.
Trade-offs and limitations
Setting maxThreads too high increases memory overhead from thread stack allocation and can increase context switching. It does not linearly increase throughput once the application or database is saturated.
An overly large acceptCount can hide latency issues by queuing requests that will eventually time out before being processed. A small acceptCount surfaces saturation faster and fails fast.
maxConnections protects against memory exhaustion from many idle keep-alive sockets, but it also limits legitimate concurrency for clients that reuse connections.
These settings are connector level. They do not replace application tuning, connection pool sizing, or JVM heap settings.
How to check the result
Check the current settings by reviewing server.xml under the Connector element for the specified attributes.
Observe behavior under load with a tool such as Apache JMeter. Vary concurrent users and watch response times and error rates as thread and connection limits are approached.
Monitor JVM thread counts and memory usage via JConsole or VisualVM during peak load simulations to confirm the worker pool stays within the configured maxThreads and that connection counts stay within maxConnections.
Adjust one parameter at a time and re-measure. The goal is stable latency under expected load with headroom, not maximum theoretical throughput.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.