Understanding Spring Boot 2.7.10’s Embedded Tomcat: Defaults, Thread Pools, and Connection Limits
This article examines Spring Boot 2.7.10’s embedded Tomcat configuration, detailing default connection queue sizes, thread pool parameters, key Tomcat settings such as AcceptCount and MaxConnections, internal thread components, and practical testing results that reveal how connection limits affect client‑server handshakes.
Overview
Spring Boot 2.7.10 bundles Tomcat 9.0.73 as its embedded servlet container. The following sections describe the default Tomcat configuration, the relevant source‑code locations, the internal thread model, and how to verify the settings with a simple test.
Default Tomcat Settings (Spring Boot 2.7.10)
Connection waiting queue length ( accept-count): 100
Maximum connections ( max-connections): 8192
Minimum spare worker threads ( min-spare): 10
Maximum worker threads ( max): 200
Connection timeout ( connection-timeout): 20 000 ms (20 s)
Keep‑alive timeout ( keep-alive-timeout): 20 000 ms (‑1 disables)
Maximum keep‑alive requests ( max-keep-alive-requests): 100
server:
tomcat:
accept-count: 100
max-connections: 8192
threads:
min-spare: 10
max: 200
connection-timeout: 20000
keep-alive-timeout: 20000
max-keep-alive-requests: 100Key Tomcat Parameters
AcceptCount
Defines the size of the TCP backlog queue (similar to Linux somaxconn). Tomcat binds the server socket with this value in org.apache.tomcat.util.net.NioEndpoint:
ServerSocketChannel serverSock = ServerSocketChannel.open();
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr, getAcceptCount());MaxConnections
Managed by org.apache.tomcat.util.net.Acceptor. When the limit is reached the acceptor thread blocks on a latch until a slot frees:
while (!stopCalled) {
connectionLimitLatch.countUpOrAwait();
Socket socket = endpoint.serverSocketAccept();
// process socket …
}MinSpareThread / MaxThread
Created in org.apache.tomcat.util.net.AbstractEndpoint.createExecutor(). Tomcat builds a custom ThreadPoolExecutor with core pool size = minSpareThreads and maximum = maxThreads:
executor = new ThreadPoolExecutor(
getMinSpareThreads(),
getMaxThreads(),
60, TimeUnit.SECONDS,
taskqueue,
tf);MaxKeepAliveRequests
Controls how many HTTP requests a persistent connection may serve. Values:
0 or 1 – disables keep‑alive
-1 – unlimited
int max = protocol.getMaxKeepAliveRequests();
if (max == 1) {
keepAlive = false;
} else if (max > 0 && socketWrapper.decrementKeepAlive() <= 0) {
keepAlive = false;
}ConnectionTimeout
Time Tomcat waits for a request line after a connection is accepted. Default = 20 000 ms. Too short drops legitimate clients; too long reduces responsiveness.
KeepAliveTimeout
Time to wait for the next request on an idle keep‑alive connection. If unset, connectionTimeout is used; -1 disables the timeout.
Internal Thread Model
Acceptor
Continuously accepts new sockets and registers them with the poller.
public void run() {
while (!stopCalled) {
Socket socket = endpoint.serverSocketAccept();
endpoint.setSocketOptions(socket);
poller.register(socketWrapper);
}
}Poller
Uses a NIO Selector to detect ready events, creates SocketProcessor tasks and hands them to the executor.
while (true) {
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey sk = it.next();
it.remove();
NioSocketWrapper sw = (NioSocketWrapper) sk.attachment();
if (sw != null) {
processKey(sk, sw);
executor.execute(new SocketProcessor(sw, SocketEvent.OPEN_READ));
}
}
}TomcatThreadPoolExecutor
Extends java.util.concurrent.ThreadPoolExecutor to track submitted tasks and to provide a custom TaskQueue that can force tasks into the queue when the pool is saturated.
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 q = (TaskQueue) super.getQueue();
try {
if (!q.force(command, timeout, unit)) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException("queueFull");
}
} catch (InterruptedException e) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException(e);
}
} else {
submittedCount.decrementAndGet();
throw rx;
}
}
}
}TaskQueue (Custom Queue)
The queue overrides offer so that new threads are created up to the maximum pool size before tasks are enqueued. It also provides a force method to bypass capacity checks.
public boolean offer(Runnable r) {
if (parent == null) return super.offer(r);
if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(r);
if (parent.getSubmittedCount() < parent.getPoolSize()) return super.offer(r);
if (parent.getPoolSize() < parent.getMaximumPoolSize()) return false; // trigger new thread
return super.offer(r);
}
public boolean force(Runnable o) {
if (parent == null || parent.isShutdown()) {
throw new RejectedExecutionException("taskQueue.notRunning");
}
return super.offer(o);
}Testing the Configuration
Sample application.yml used to observe the effect of a small backlog and thread pool:
server:
port: 8080
tomcat:
accept-count: 3
max-connections: 6
threads:
min-spare: 2
max: 3Run ss -nltp (or netstat -nltp) to view the socket state. Increase concurrent connections and note the behavior:
When total connections exceed max-connections + accept-count, new SYN packets remain in SYN_RECV on the server and SYN_SENT on the client, causing a 20 s handshake timeout.
If the client also sets a timeout, the shorter of the two determines when the connection is aborted.
Clients must align their own connection‑establishment timeout with the server’s 20 s limit to avoid premature failures.
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.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow 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.
