Understanding Tomcat Configuration and Thread Management in Spring Boot 2.7.10
This article explains the default Tomcat settings bundled with Spring Boot 2.7.10, details core parameters such as accept‑count, max‑connections, thread pool sizes, and timeouts, describes the internal Acceptor and Poller threads, provides configuration examples and testing results, and includes reference links.
In Spring Boot 2.7.10 the embedded Tomcat version is 9.0.73, and its default settings are:
Connection waiting queue length (accept‑count): 100
Maximum connections (max‑connections): 8192
Minimum worker threads (min‑spare): 10
Maximum worker threads (max‑threads): 200
Connection timeout: 20 s
The corresponding YAML configuration is:
server:
tomcat:
# When all request‑handling threads are busy, the maximum queue length for incoming connections
accept-count: 100
# Maximum number of connections the server can accept at any time
max-connections: 8192
threads:
# Minimum number of worker threads created at startup
min-spare: 10
# Maximum number of worker threads (IO‑intensive workloads usually need 10×CPU cores)
max: 200
# Time (ms) to wait for a request line after a connection is accepted
connection-timeout: 20000
# Time (ms) to keep a connection alive when no further request arrives
keep-alive-timeout: 20000
# Maximum number of keep‑alive requests before the server closes the connection
max-keep-alive-requests: 100Architecture Diagram
When the total number of connections exceeds maxConnections + acceptCount + 1, new requests are not rejected immediately; instead they fail to complete the three‑way TCP handshake and eventually time out after the client’s timeout or Tomcat’s 20 s timeout.
TCP Three‑Way Handshake and Four‑Way Teardown
Sequence Diagram
Core Parameters
AcceptCount
Represents the full connection queue capacity, equivalent to the backlog parameter; it is compared with the Linux somaxconn value and takes the smaller one.
Relevant code (NioEndpoint.java):
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
// bind with accept‑count as backlog
serverSock.socket().bind(addr, getAcceptCount());MaxConnections
Handled in Acceptor.java:
// Thread run method
public void run() {
while (!stopCalled) {
// If we have reached the max connections, wait
connectionLimitLatch.countUpOrAwait();
// Accept next incoming connection from server socket
socket = endpoint.serverSocketAccept();
// socket.close will trigger connectionLimitLatch.countDown();
}
}MinSpareThread / MaxThread
Configured in AbstractEndpoint.java:
// Tomcat startup
public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS, taskqueue, tf);
taskqueue.setParent((ThreadPoolExecutor) executor);
}Tomcat extends the JDK thread pool to prioritize thread creation before queuing tasks.
MaxKeepAliveRequests
When the number of keep‑alive requests reaches this value, the server proactively closes the connection. Setting it to 0 or 1 disables keep‑alive; setting it to –1 allows unlimited keep‑alive requests.
NioEndpoint.setSocketOptions
socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
Http11Processor.service(SocketWrapperBase<?> socketWrapper)
keepAlive = true;
while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
sendfileState == SendfileState.DONE && !protocol.isPaused()) {
// default 100
int maxKeepAliveRequests = protocol.getMaxKeepAliveRequests();
if (maxKeepAliveRequests == 1) {
keepAlive = false;
} else if (maxKeepAliveRequests > 0 && socketWrapper.decrementKeepAlive() <= 0) {
keepAlive = false;
}
}ConnectionTimeout
The lifetime of an established connection; if no request arrives within this period, the server closes the connection. In Tomcat 9 the default is 20 000 ms (20 s).
// Check read timeout
if ((socketWrapper.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
long delta = now - socketWrapper.getLastRead();
long timeout = socketWrapper.getReadTimeout();
if (timeout > 0 && delta > timeout) {
readTimeout = true;
}
}
// Check write timeout
if (!readTimeout && (socketWrapper.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
long delta = now - socketWrapper.getLastWrite();
long timeout = socketWrapper.getWriteTimeout();
if (timeout > 0 && delta > timeout) {
writeTimeout = true;
}
}KeepAliveTimeout
Time to wait for another HTTP request before closing the connection. If not set, connectionTimeout is used; a value of –1 disables the timeout.
// Read new bytes if needed
if (byteBuffer.position() >= byteBuffer.limit()) {
if (keptAlive) {
// No request data yet, use keep‑alive timeout
wrapper.setReadTimeout(keepAliveTimeout);
}
if (!fill(false)) {
parsingRequestLinePhase = 1;
return false;
}
// At least one byte of request received, switch to socket timeout
wrapper.setReadTimeout(connectionTimeout);
}Internal Threads
Acceptor
The Acceptor receives socket connections, wraps them into NioSocketWrapper, and registers them with the Poller.
public void run() {
while (!stopCalled) {
// Wait for next request
socket = endpoint.serverSocketAccept();
// Register socket to Poller, generate PollerEvent
endpoint.setSocketOptions(socket);
// Add new socket to poller events queue
poller.register(socketWrapper);
}
}Poller
The Poller continuously checks the NIO selector for ready keys, retrieves the associated NioSocketWrapper, and dispatches the request to the thread pool.
public void run() {
while (true) {
Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
if (socketWrapper != null) {
processKey(sk, socketWrapper);
executor.execute(new SocketProcessor(socketWrapper, SocketEvent));
}
}
}
}TomcatThreadPoolExecutor
Tomcat’s custom thread pool extends the JDK ThreadPoolExecutor with a more efficient getSubmittedCount() and a forced‑queue mechanism.
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
private final AtomicInteger submittedCount = new AtomicInteger(0);
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (!(t instanceof StopPooledThreadException)) {
submittedCount.decrementAndGet();
}
}
@Override
public void execute(Runnable command) {
submittedCount.incrementAndGet();
try {
super.execute(command);
} catch (RejectedExecutionException rx) {
if (super.getQueue() instanceof TaskQueue) {
TaskQueue queue = (TaskQueue) super.getQueue();
try {
if (!queue.force(command, timeout, unit)) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException("threadPoolExecutor.queueFull");
}
} catch (InterruptedException x) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException(x);
}
} else {
submittedCount.decrementAndGet();
throw rx;
}
}
}
}Testing
Example configuration for testing different connection limits:
server:
port: 8080
tomcat:
accept-count: 3
max-connections: 6
threads:
min-spare: 2
max: 3Use ss -nltp to view the current connection queue size. The article shows results for 6, 9, 10, 11, 12 concurrent connections, illustrating how excess connections remain in SYN_RECV or SYN_SENT state and eventually time out.
If the client sets a timeout, the smaller of the client timeout and the server’s three‑way handshake timeout will determine the final outcome.
References
https://www.zhangbj.com/p/1105.html
https://www.eginnovations.com/blog/tomcat-monitoring-metrics/
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
