Backend Development 15 min read

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 connection queues, thread pools, keep‑alive limits, and timeouts, and demonstrates how to monitor and test these settings with code examples and network tools.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Understanding Tomcat Configuration and Thread Management in Spring Boot 2.7.10

Overview

The author, a self‑described "architect who writes code and poetry", presents a deep dive into the Tomcat server embedded in Spring Boot 2.7.10, covering default configurations, internal thread architecture, key parameters, and practical testing methods.

Default Tomcat Settings in Spring Boot 2.7.10

Spring Boot 2.7.10 ships with Tomcat 9.0.73. The default values are:

Connection accept queue length (accept‑count): 100

Maximum connections: 8192

Minimum worker threads: 10

Maximum worker threads: 200

Connection timeout: 20 seconds

Configuration snippet (application.yml):

server:
  tomcat:
    # Maximum length of the queue when all request‑handling threads are busy
    accept-count: 100
    # Maximum number of connections the server will accept at any time
    max-connections: 8192
    threads:
      # Minimum number of spare threads created at startup
      min-spare: 10
      # Maximum number of worker threads (IO‑intensive workloads usually need ~10×CPU cores)
      max: 200
    connection-timeout: 20000
    keep-alive-timeout: 20000
    max-keep-alive-requests: 100

Core Parameters Explained

AcceptCount

Defines the size of the connection backlog (similar to the Linux somaxconn value). It limits how many pending connections can wait before being accepted.

MaxConnections

Maximum simultaneous connections that Tomcat will handle. When the limit is reached, new connections are queued or rejected.

MinSpareThread / MaxThread

Thread pool configuration: min-spare is the number of idle threads kept ready; max is the upper bound of worker threads. Tomcat uses a custom ThreadPoolExecutor that extends the JDK pool with additional metrics.

MaxKeepAliveRequests

After this many requests on a persistent connection, Tomcat will close the connection. Setting it to -1 disables the limit.

ConnectionTimeout

Time (in ms) a connection can stay idle after being established before the server closes it.

KeepAliveTimeout

Time to wait for the next HTTP request on a keep‑alive connection; defaults to connectionTimeout unless overridden. -1 means no timeout.

Internal Thread Model

Acceptor

Accepts incoming socket connections, configures them via setSocketOptions() , and registers them with the poller.

public void run() {
    while (!stopCalled) {
        socket = endpoint.serverSocketAccept();
        endpoint.setSocketOptions(socket);
        poller.register(socketWrapper);
    }
}

Poller

Continuously polls the NIO Selector for ready events, extracts SelectionKey objects, and dispatches them to the executor thread pool.

public void run() {
    while (true) {
        Iterator
iterator = selector.selectedKeys().iterator();
        while (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.OPEN_READ/SocketEvent.OPEN_WRITE));
            }
        }
    }
}

TomcatThreadPoolExecutor

Extends java.util.concurrent.ThreadPoolExecutor with a submittedCount metric and 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) {
            // force task into queue or re‑throw
        }
    }
}

TaskQueue

A custom LinkedBlockingQueue that can force insertion of tasks, report remaining capacity, and stop idle threads when the pool shuts down.

Testing the Configuration

Example minimal configuration used for testing:

server:
  port: 8080
  tomcat:
    accept-count: 3
    max-connections: 6
    threads:
      min-spare: 2
      max: 3

Network inspection commands:

ss -nltp
ss -nlt | grep 8080

Observations for different concurrent connection counts (6, 9, 10, 11, 12):

When the total connections exceed maxConnections + acceptCount + 1 , new clients remain in SYN_RECV state and eventually time out after the 20 s handshake timeout.

Client‑side sockets stay in SYN_SENT until the server drops the handshake.

Increasing the accept queue shows the same pattern, confirming the importance of tuning acceptCount and maxConnections .

References

ZhangBJ article

Tomcat monitoring metrics

backendJavaSpring Bootthread poolTomcatConnection Timeout
Java Architect Essentials
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.