Appearance
Spring Boot: Bean Scopes | Singleton, Prototype, Request, Session Scopes with Examples
Real World Analogy
Imagine a coffee shop:
- Singleton: There is one cash register. Every customer uses the same register.
- Prototype: Every customer gets their own cup. A new cup is made for each order.
- Request: A new receipt is printed for each purchase. Once the purchase is done, the receipt is discarded.
- Session: A loyalty card is created when a customer joins. It stays active across multiple visits until they cancel their membership.
These four scenarios map directly to the four main Spring bean scopes.
Prerequisites
Bean scopes build directly on the bean lifecycle covered in the previous chapter. Recall the lifecycle:
- IoC container starts
- Bean discovery
- Bean construction
- Dependency injection
@PostConstruct- Bean in use
@PreDestroy- Bean destroyed
Understanding when a bean is constructed (step 3) is the key to understanding scopes. Different scopes control how many instances are created and when those instances are created.
The Five Bean Scopes
| Scope | Description | Initialisation |
|---|---|---|
| Singleton | One instance per IoC container | Eager (at startup) |
| Prototype | New instance every time | Lazy (on each request) |
| Request | One instance per HTTP request | Lazy |
| Session | One instance per HTTP session | Lazy |
| Application | One instance across multiple IoC containers | Eager |
Scope 1: Singleton (Default)
Definition: Only one instance is created per IoC container. Every class or bean that depends on a singleton receives the same object.
This is the default scope. If you do not specify a scope, Spring treats the bean as singleton.
java
import org.springframework.stereotype.Component;
// All three declarations below result in the same singleton scope:
@Component // default — singleton
public class UserRepository { }
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) // explicit enum
public class UserRepository { }
@Component
@Scope("singleton") // explicit string
public class UserRepository { }Demonstrating Singleton Behaviour
java
@Component
public class UserRepository {
public UserRepository() {
System.out.println("[UserRepository] Created — hashCode: " + this.hashCode());
}
}
@RestController
public class ControllerA {
private final UserRepository userRepository;
@Autowired
public ControllerA(UserRepository userRepository) {
this.userRepository = userRepository;
System.out.println("[ControllerA] userRepository hashCode: " + userRepository.hashCode());
}
}
@RestController
public class ControllerB {
private final UserRepository userRepository;
@Autowired
public ControllerB(UserRepository userRepository) {
this.userRepository = userRepository;
System.out.println("[ControllerB] userRepository hashCode: " + userRepository.hashCode());
}
}Expected startup output:
[UserRepository] Created — hashCode: 112358132
[ControllerA] userRepository hashCode: 112358132
[ControllerB] userRepository hashCode: 112358132Both controllers receive the same UserRepository instance. After startup, subsequent API calls do not create any new objects. The singleton object is used for every request.
When to Use Singleton
Use singleton for stateless components: services, repositories, clients, caches. Most beans in a Spring application are singletons.
Scope 2: Prototype
Definition: A new instance is created every time the bean is requested from the container.
java
import org.springframework.context.annotation.Scope;
@Component
@Scope("prototype")
// or @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ReportBuilder {
public ReportBuilder() {
System.out.println("[ReportBuilder] New instance — hashCode: " + this.hashCode());
}
}Demonstrating Prototype Behaviour
java
@Component
@Scope("prototype")
public class UserSession {
public UserSession() {
System.out.println("[UserSession] Created — hashCode: " + this.hashCode());
}
}
@Component
public class Student {
// Student is singleton — its UserSession dependency is resolved once
private final UserSession userSession;
@Autowired
public Student(UserSession userSession) {
this.userSession = userSession;
System.out.println("[Student] userSession hashCode: " + userSession.hashCode());
}
}
@RestController
@Scope("prototype")
public class ApiController {
private final UserSession userSession;
private final Student student;
@Autowired
public ApiController(UserSession userSession, Student student) {
this.userSession = userSession;
this.student = student;
}
@GetMapping("/fetch")
public String fetch() {
return "controller userSession: " + userSession.hashCode()
+ ", student userSession: " + student.getUserSession().hashCode();
}
}What happens at startup:
ApiControlleris prototype → not created at startupStudentis singleton → eagerly created at startupStudentneeds aUserSession(prototype) → oneUserSessionis created forStudent
What happens on first API call to /fetch:
- A new
ApiControlleris created (prototype) - A new
UserSessionis created forApiController(prototype) Studentreuses its ownUserSession(singleton uses same prototype instance forever)
Important: When a singleton bean depends on a prototype bean, the singleton holds the same prototype instance forever. It does not get a new prototype on every method call. To get a new prototype instance on each call from within a singleton, you need to use ApplicationContext.getBean() or ObjectProvider<T>.
When to Use Prototype
Use prototype for stateful objects that should not be shared: user specific data structures, report builders, form objects. Avoid it for heavyweight beans that are expensive to create.
Scope 3: Request
Definition: A new bean instance is created for each HTTP request. Within a single request, the same instance is shared across all components that need it. When the request ends, the bean is destroyed.
java
import org.springframework.web.context.annotation.RequestScope;
@Component
@RequestScope
// equivalent to @Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class RequestContext {
private String requestId;
private String userId;
public RequestContext() {
System.out.println("[RequestContext] Created for new HTTP request");
}
// getters and setters
}Demonstrating Request Scope
java
@Component
@RequestScope
public class UserContext {
public UserContext() {
System.out.println("[UserContext] Created — hashCode: " + this.hashCode());
}
}
@RestController
public class OrderController {
private final UserContext userContext;
private final OrderProcessor orderProcessor;
@Autowired
public OrderController(UserContext userContext, OrderProcessor orderProcessor) {
this.userContext = userContext;
this.orderProcessor = orderProcessor;
}
@GetMapping("/order")
public String placeOrder() {
// Both this controller and OrderProcessor see the SAME UserContext instance
// within this single HTTP request
return "controller UserContext: " + userContext.hashCode()
+ ", processor UserContext: " + orderProcessor.getUserContext().hashCode();
}
}
@Component
@Scope("prototype")
public class OrderProcessor {
private final UserContext userContext;
@Autowired
public OrderProcessor(UserContext userContext) {
this.userContext = userContext;
}
public UserContext getUserContext() { return userContext; }
}First HTTP request: One UserContext created. Both OrderController and OrderProcessor share it. Second HTTP request: A brand new UserContext is created. The previous one is discarded.
The Proxy Mode Problem
Consider this scenario: a singleton bean depends on a request scoped bean.
java
@RestController // Singleton — eagerly initialised at startup
public class DashboardController {
@Autowired
private UserContext userContext; // Request scoped
@GetMapping("/dashboard")
public String show() {
return userContext.getUserId();
}
}At application startup, Spring tries to create DashboardController. It looks for a UserContext bean to inject. But UserContext is request scoped — it can only exist within an active HTTP request. At startup, there is no HTTP request. The application fails.
Solution: ScopedProxyMode
java
@Component
@Scope(
value = WebApplicationContext.SCOPE_REQUEST,
proxyMode = ScopedProxyMode.TARGET_CLASS // Create a proxy at startup
)
public class UserContext {
private String userId;
// ...
}With proxyMode = ScopedProxyMode.TARGET_CLASS, Spring creates a proxy object at startup and injects that proxy into the singleton. The proxy looks like a real UserContext but delegates all actual calls to the real, request bound instance. When an HTTP request arrives and you call userContext.getUserId(), the proxy transparently resolves the actual request scoped bean and forwards the call.
Startup:
DashboardController ← proxy(UserContext) [no real UserContext yet]
HTTP Request arrives:
proxy(UserContext) → real UserContext for this request → getUserId()
HTTP Request ends:
real UserContext destroyed
proxy still alive, ready for next requestScope 4: Session
Definition: One instance per HTTP session. Unlike request scope (one per request), session scope creates an instance when the session is first used and keeps it alive until the session expires or is explicitly invalidated.
java
import org.springframework.web.context.annotation.SessionScope;
@Component
@SessionScope
// equivalent to @Scope(value = WebApplicationContext.SCOPE_SESSION)
public class ShoppingCart {
private List<String> items = new ArrayList<>();
public void addItem(String item) { items.add(item); }
public List<String> getItems() { return items; }
public ShoppingCart() {
System.out.println("[ShoppingCart] Created for new HTTP session");
}
}Demonstrating Session Scope
java
@RestController
public class CartController {
private final ShoppingCart cart;
@Autowired
public CartController(ShoppingCart cart) {
this.cart = cart;
}
@PostMapping("/cart/add")
public String addItem(@RequestParam String item) {
cart.addItem(item);
return "Added: " + item;
}
@GetMapping("/cart")
public List<String> viewCart() {
return cart.getItems();
}
@PostMapping("/logout")
public String logout(HttpServletRequest request) {
request.getSession().invalidate(); // ends session, destroys ShoppingCart
return "Logged out";
}
}Lifecycle:
- User hits
/cart/addfor the first time → HTTP session created →ShoppingCartbean created - User calls
/cart/addagain → same session active → sameShoppingCartinstance used (items persist) - User calls
/logout→ session invalidated →ShoppingCartdestroyed - User calls
/cart/addagain → new session → newShoppingCartcreated from scratch
A singleton bean depending on a session scoped bean has the same proxy problem as with request scope. Use proxyMode = ScopedProxyMode.TARGET_CLASS on the session scoped bean.
Scope 5: Application
Definition: One instance shared across multiple IoC containers within the same ServletContext. Functionally very similar to singleton (one object per application), but the distinction matters when multiple Spring contexts are running in the same JVM.
java
@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
public class GlobalConfig {
private String appName = "MyApp";
// getters, setters
}In practice, most applications run a single IoC container. Application scope is rarely needed, but it is worth knowing for completeness.
Putting It All Together
java
// Singleton — one instance, created at startup
@Service
public class ProductService { }
// Prototype — new instance every time it is requested
@Component
@Scope("prototype")
public class CartItem { }
// Request — one per HTTP request, proxy needed when injected into singletons
@Component
@RequestScope
public class RequestLogger { }
// Session — one per user session, proxy needed when injected into singletons
@Component
@SessionScope
public class UserPreferences { }Summary
| Scope | Instances | Created When | Destroyed When |
|---|---|---|---|
| Singleton | One per IoC container | App startup | App shutdown |
| Prototype | New for each injection/lookup | On demand | When GC collected |
| Request | One per HTTP request | First use in request | Request ends |
| Session | One per HTTP session | First use in session | Session expires/invalidated |
| Application | One per ServletContext | App startup | App shutdown |
Interview Questions
Q1. What is the default scope of a Spring bean? Singleton. If you do not specify a scope, Spring creates exactly one instance of the bean per IoC container and reuses it everywhere.
Q2. What is the difference between singleton and prototype scope? Singleton creates one instance per IoC container, shared across all usages. Prototype creates a fresh instance every time the bean is requested from the container. Singletons are eagerly initialised; prototypes are lazily initialised.
Q3. What is the difference between request scope and session scope? Request scope creates a new bean for each HTTP request and destroys it when the request ends. Session scope creates a bean when an HTTP session begins and keeps it alive for the duration of the session (across multiple requests) until the session expires or is invalidated.
Q4. What is the proxy mode and why is it needed? When a shorter lived bean (request or session scoped) is injected into a longer lived bean (singleton), Spring cannot inject the real bean at startup because it does not exist yet. ScopedProxyMode.TARGET_CLASS tells Spring to inject a proxy instead. The proxy delegates method calls to the actual bean at runtime, which is created when the appropriate scope (request, session) becomes active.
Q5. If a singleton bean depends on a prototype bean, does it get a new prototype on every method call? No. The singleton holds the same prototype instance it received when it was first created. To get a new prototype instance on each method call from within a singleton, you need to use ObjectProvider<T> or look up the bean directly from ApplicationContext.getBean().
Q6. Why is prototype scope said to be lazily initialised? Prototype beans are not created at application startup. They are only created when something actually requests them — either because a dependency injection point triggers it, or because code calls applicationContext.getBean(). This contrasts with singleton beans, which are eagerly created at startup.
Q7. What is the application scope? How does it differ from singleton? Application scope creates one bean instance per ServletContext, which means one instance shared across multiple IoC containers if multiple exist in the same JVM. Singleton scope creates one instance per IoC container. In typical applications with one container, they behave identically.
Q8. How do you declare a prototype scoped bean? Annotate the class with @Scope("prototype") or @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) in addition to the stereotype annotation (e.g., @Component).