Backend Development 11 min read

Understanding Thread Safety of Spring Beans: Singleton vs Prototype Scope

This article explains why Spring beans are not inherently thread‑safe, compares the singleton and prototype scopes, shows how stateless beans can be safe, demonstrates the impact of static fields and injected beans, and provides practical code examples and recommendations for achieving thread safety in Spring applications.

Top Architect
Top Architect
Top Architect
Understanding Thread Safety of Spring Beans: Singleton vs Prototype Scope

Conclusion: Spring beans are not thread‑safe by default; the container does not enforce any thread‑safety policy.

The Spring bean scopes are:

singleton – default, one instance per container.

prototype – a new instance is created on each request.

request – one instance per HTTP request (WebApplicationContext only).

session – one instance per HTTP session.

global‑session – one instance for all sessions.

For prototype beans , each request gets a fresh instance, so there is no shared state and thus no thread‑safety issue.

For singleton beans , all threads share the same instance, which can lead to resource contention. However, if the bean is stateless (e.g., typical @Controller, @Service, @Dao classes that only operate on method‑local data), it behaves as thread‑safe because no mutable shared state is accessed.

If a bean holds mutable state, developers must ensure thread safety themselves, often by changing the bean’s scope to prototype so a new instance is created per request.

Example controller demonstrating the problem:

@RestController
public class TestController {
    private int var = 0;
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通变量var:" + (++var));
        return "普通变量var:" + var;
    }
}

When called three times, the output shows the variable increments (1, 2, 3), proving the singleton controller is not thread‑safe.

Adding @Scope("prototype") makes each request receive a new controller instance, resetting the variable to 1 each time.

Static fields remain shared across all instances, regardless of scope, and therefore stay non‑thread‑safe. Injected beans (e.g., a User bean) are also singleton by default, sharing state across requests.

To make injected beans thread‑safe, declare them with @Scope("prototype") in the configuration:

@Configuration
public class MyConfig {
    @Bean
    @Scope("prototype")
    public User user() {
        return new User();
    }
}

Running the same requests after this change shows different hashCode values for the User bean, confirming a new instance per request.

Key take‑aways:

Spring controllers and services are singleton by default and thus not thread‑safe if they hold mutable state.

Avoid static variables in beans; they are always shared.

Injected beans are singleton unless their scope is explicitly set to prototype .

For mutable per‑request state, use ThreadLocal or define the bean with prototype scope.

JavaSpringThread SafetyBeansingletonPrototypeScope
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

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.