Bean Scopes
A bean’s scope defines how many instances the IoC container creates and how long each one lives. By default Spring creates a single shared instance per container, but for stateful or web-bound beans you can ask for a new instance per injection, per HTTP request, or per session. Choosing the right scope is essential for correctness and thread safety.
The available scopes
Spring ships six built-in scopes. The last four are only available in a web-aware application context.
| Scope | Instances created | Lifetime | Web only |
|---|---|---|---|
singleton (default) | One per container | Entire application | No |
prototype | One per injection / lookup | Until garbage collected | No |
request | One per HTTP request | Duration of the request | Yes |
session | One per HTTP session | Duration of the session | Yes |
application | One per ServletContext | Entire web application | Yes |
websocket | One per WebSocket session | Duration of the WebSocket | Yes |
Note:
applicationscope differs subtly fromsingleton. A singleton is one-per-ApplicationContext; anapplication-scoped bean is one-per-ServletContext, which can span multiple contexts in the same servlet container.
Singleton — the default
Every bean is a singleton unless you say otherwise. One instance is created eagerly at startup and shared everywhere it is injected. This is efficient but demands that the bean be stateless and thread-safe, because all threads share it.
@Service
public class PricingService {
// no mutable instance fields — safe to share across threads
public double withTax(double amount) {
return amount * 1.20;
}
}
Prototype — a fresh instance every time
A prototype bean is created anew on each injection point or getBean call. Use it for stateful, short-lived helpers.
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // or @Scope("prototype")
public class ReportBuilder {
private final List<String> lines = new ArrayList<>();
public void addLine(String line) {
lines.add(line);
}
}
Warning: The container does not run destruction callbacks (
@PreDestroy) for prototype beans — it hands the instance off and forgets it. Release resources yourself. See Beans & Lifecycle.
Web scopes
The web scopes bind a bean’s lifetime to the current HTTP interaction. They are declared with @Scope or the convenience annotations @RequestScope, @SessionScope, and @ApplicationScope.
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.stereotype.Component;
@Component
@RequestScope
public class RequestContext {
private String correlationId;
// a fresh instance is created for each incoming HTTP request
}
Injecting a shorter-lived bean into a singleton
This is the classic scoping pitfall. A singleton is created once at startup, so if you inject a request- or prototype-scoped bean directly, the singleton captures one instance forever — defeating the narrower scope.
The fix is a scoped proxy. Spring injects a lazy proxy into the singleton; on each method call the proxy resolves the correct instance for the current request or lookup.
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.stereotype.Component;
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String correlationId;
public String getCorrelationId() {
return correlationId;
}
public void setCorrelationId(String id) {
this.correlationId = id;
}
}
@Service
public class AuditService {
private final RequestContext requestContext; // actually a proxy
public AuditService(RequestContext requestContext) {
this.requestContext = requestContext;
}
public void record(String action) {
// proxy delegates to the request-scoped instance for THIS request
System.out.println(requestContext.getCorrelationId() + ": " + action);
}
}
ScopedProxyMode.TARGET_CLASS uses CGLIB to proxy the concrete class. Use ScopedProxyMode.INTERFACES instead when the bean is injected through an interface.
Output: (two concurrent requests sharing the singleton AuditService)
req-abc-111: login
req-xyz-222: login
Each request sees its own correlationId even though AuditService is a single shared instance.
Tip:
@RequestScopeand@SessionScopedefault toScopedProxyMode.TARGET_CLASSalready, so injecting them into singletons “just works” without specifyingproxyModeexplicitly.
Choosing a scope
- Default to singleton and keep the bean stateless — this covers the vast majority of services, repositories, and controllers.
- Use prototype only for genuinely stateful, throwaway objects, and manage their cleanup yourself.
- Use request / session for per-user web state, and remember to add a scoped proxy when injecting them into singletons.
- Reach for application / websocket rarely, for state tied to the servlet context or a socket session.