Introduction
When building Spring Boot applications, a common question developers ask is:
"What happens when multiple requests hit the same controller at the same time?"
In this article, we’ll dive deep into how Spring Boot handles concurrent requests, what happens under the hood, and how you can design controllers that are both efficient and thread-safe.
1. The Lifecycle of a Controller in Spring Boot
By default, Spring Boot creates controllers as singleton beans.
-
Only one instance of the controller exists in the application context.
-
That single instance can handle multiple concurrent requests — thanks to the thread-per-request model used by the servlet container (Tomcat, Jetty, or Undertow).
Diagram 1 – Request Flow to a Singleton Controller
javaMultiple HTTP Requests │ │ │ ▼ ▼ ▼ ┌─────────────────────────────┐ │ Servlet Container (Tomcat) │ └─────────────┬───────────────┘ │ Each request gets its own thread │ ▼ ┌─────────────────────────────┐ │ Singleton Controller │ └─────────────────────────────┘
2. Thread-per-Request Model
Here’s how concurrent requests are processed:
-
The HTTP request arrives at the server.
-
The servlet container assigns a separate thread from its thread pool to handle the request.
-
The request is mapped to the appropriate controller method by the
DispatcherServlet
. -
The method executes independently on that thread, then returns a response.
Tomcat default configuration:
-
maxThreads = 200
→ up to 200 requests can run in parallel. -
Any extra requests wait in a queue until a thread is free.
Diagram 2 – Multiple Threads Calling the Same Controller
scssThread-1 → Controller method() Thread-2 → Controller method() Thread-3 → Controller method() ... all running on the same controller instance
3. Thread Safety Concerns
Since there’s only one instance of the controller:
-
Avoid mutable instance variables that can be modified during a request.
-
Use method-local variables or immutable objects for request-specific data.
Thread-unsafe example:
java@RestController public class CounterController { private int counter = 0; // Shared mutable state @GetMapping("/count") public int increment() { counter++; // Race condition risk return counter; } }
Thread-safe example:
java@RestController public class CounterController { @GetMapping("/count") public int increment() { int counter = 0; // Local variable counter++; return counter; } }
4. Key Takeaways
-
Spring Boot controllers are singleton by default.
-
Multiple requests are handled concurrently using separate threads.
-
Controllers must be stateless or carefully manage shared state.
-
Use local variables for request-specific data to ensure thread safety.
Diagram 3 – Safe Controller Design
pgsqlSingleton Controller │ ├── No shared mutable fields └── All request data stored in method-local variables
Conclusion
Understanding how Spring Boot processes multiple requests is essential for writing scalable, thread-safe, and reliable applications.
By following these practices, you ensure your controllers can handle high concurrency without race conditions or data corruption.
0 Comments