Understanding Spring MVC Controller Singleton Scope and Thread Safety

The article explains that Spring MVC Controllers are singleton beans by default, discusses the thread‑safety risks of using instance variables in such Controllers, and presents several solutions—including prototype scope, ThreadLocal, and AtomicInteger—to ensure safe concurrent handling of requests.

Top Architect
Top Architect
Top Architect
Understanding Spring MVC Controller Singleton Scope and Thread Safety

In Spring MVC, a @Controller is a singleton bean by default, which means all incoming HTTP requests share the same instance. While the singleton pattern can reduce memory usage, it also introduces thread‑safety problems when instance variables are accessed concurrently.

Tomcat creates a dedicated thread for each request; if more requests arrive than available threads, Tomcat creates additional threads up to maxThreads and then queues excess connections, eventually refusing them when limits are exceeded (source: Tomcat documentation).

Because each servlet in Tomcat is a singleton, Spring MVC Controllers inherit this behavior. The default singleton scope allows high concurrency but makes any mutable state in the Controller shared across threads.

Using an instance variable such as private int num inside a Controller leads to race conditions: multiple threads may read and modify num simultaneously, breaking idempotency and causing inconsistent results.

@Controller
public class TestController {
    private int num = 0;

    @RequestMapping("/addNum")
    public void addNum() {
        System.out.println(++num);
    }
}

When the endpoint /addNum is called twice, the first request prints 1 and the second prints 2, demonstrating that the shared variable is modified across requests.

To make Controllers thread‑safe, the article suggests several approaches:

Avoid defining mutable instance variables in Controllers.

If a variable is required, change the bean scope to prototype: @Scope("prototype"), which creates a new instance per request.

Use ThreadLocal to give each thread its own copy of a variable.

Prefer AtomicInteger or other atomic types for safe concurrent increments.

@Controller
@Scope(value="prototype")
public class TestController {
    private int num = 0;

    @RequestMapping("/addNum")
    public void addNum() {
        System.out.println(++num);
    }
}
public class TestController {
    private int num = 0;
    private final ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return num;
        }
    };

    @RequestMapping("/addNum")
    public void addNum() {
        int unum = uniqueNum.get();
        uniqueNum.set(++unum);
        System.out.println(uniqueNum.get());
    }
}

After applying these techniques, each request to /addNum yields a consistent result (e.g., always 1 when using ThreadLocal), preserving idempotency and preventing data races.

The overall recommendation is to avoid mutable state in Controllers or to manage it with thread‑safe constructs to ensure reliable behavior under high concurrency.

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.

JavaBackend Developmentconcurrencythread safetyControllerSpring MVCSingleton
Top Architect
Written by

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.

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.