Understanding Spring Boot's Tomcat Thread and Connection Limits: Configuration, Testing, and Concurrency Issues

This article explains how Spring Boot relies on Tomcat's thread and connection settings, demonstrates how to configure these parameters, runs a practical test to observe request handling limits, and discusses the resulting concurrency problems and their root causes.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Understanding Spring Boot's Tomcat Thread and Connection Limits: Configuration, Testing, and Concurrency Issues

Hello everyone, I am Chen.

Spring Boot uses Tomcat as its default embedded container, so the number of requests it can handle is essentially determined by Tomcat's capacity.

The default Tomcat configuration is defined in spring-configuration-metadata.json, which maps to the class org.springframework.boot.autoconfigure.web.ServerProperties.

Four parameters are related to the number of requests Tomcat can process:

server.tomcat.threads.min-spare : minimum number of worker threads (default 10). These are long‑term workers used when concurrent requests are fewer than 10.

server.tomcat.threads.max : maximum number of worker threads (default 200). These act as temporary workers for concurrent requests between 10 and the max.

server.tomcat.max-connections : maximum number of connections (default 8192). It defines the total number of requests Tomcat can accept; excess requests are placed in a waiting queue.

server.tomcat.accept-count : length of the waiting queue (default 100).

To illustrate the relationship, consider the following analogy: Tomcat is a restaurant, a request is a customer, min‑spare are the permanent chefs, max is the total number of chefs (permanent + temporary), max‑connections are the seats, and accept‑count are the stools at the entrance.

Consequently, the maximum number of requests Spring Boot can handle equals max-connections + accept-count; any request beyond this sum is dropped.

Paper knowledge is shallow; practice reveals the truth.

Below is a practical example to verify the theory.

First, create a Spring Boot project and set the following values in application.yml (the defaults are too large for testing, so we shrink them):

server:
  tomcat:
    threads:
      # Minimum number of threads
      min-spare: 10
      # Maximum number of threads
      max: 15
    # Maximum number of connections
    max-connections: 30
    # Maximum waiting queue length
    accept-count: 10

Then add a simple endpoint:

@GetMapping("/test")
public Response test1(HttpServletRequest request) throws Exception {
    log.info("ip:{}, thread:{}", request.getRemoteAddr(), Thread.currentThread().getName());
    Thread.sleep(500);
    return Response.buildSuccess();
}

The endpoint logs the thread name and sleeps for 0.5 seconds, causing some requests to wait in the queue.

Using Apifox, we simulate 100 concurrent requests:

Result:

Because max-connections + accept-count = 40, 60 requests are discarded as expected. With a maximum of 15 threads, 25 requests wait, then the first 15 are processed, followed by the next 15, and finally the remaining 10, forming three batches (15, 15, 10).

The console log also shows the highest thread number is 15, confirming the analysis.

Summary: If concurrent requests are below server.tomcat.threads.max, they are processed immediately; excess requests wait, and if the total exceeds max-connections + accept-count, the surplus is dropped.

Extension: How Concurrency Issues Arise

Having understood how many requests Spring Boot can handle, we can explore why actual concurrency results sometimes differ from expectations.

Imagine chefs record the total number of dishes prepared in a ledger. Each chef copies the current number to a draft, adds one, and writes back. If two chefs read the same value simultaneously, both add one and write back, resulting in a lost increment.

In Spring, beans are singleton by default, so the controller and service instances are shared across threads. Declaring a global variable like cookSum leads to the race condition described above.

private int cookSum = 0;

@GetMapping("/test")
public Response test1(HttpServletRequest request) throws Exception {
    // simulate cooking
    cookSum += 1;
    log.info("Made {} dishes", cookSum);
    Thread.sleep(500);
    return Response.buildSuccess();
}

To prevent this, proper locking or thread‑safe constructs are required (not covered here).

Final Note (Please Follow)

If this article helped you, please like, view, share, and bookmark—it fuels my continued writing.

Additionally, my Knowledge Planet is open; reply with the keyword "Knowledge Planet" to receive a ¥30 discount coupon, and join for ¥89 to access premium series such as Spring full‑stack practice, massive data sharding, DDD micro‑service tutorials, and more.

Follow the public account "Code Monkey Technical Column" for exclusive fan benefits and join the discussion group by replying "Add Group".

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceconcurrencySpringBootTomcat
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

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.