Skip to main content

2.4 Bean Scopes & Lifecycle

← Back to Chapter 2: Spring Beans & Dependency Injection

Understanding bean scoping rules and lifecycle interception hooks allows engineers to manage memory footprint, ensure thread safety, and implement robust resource allocation/teardown logic.


What is Bean scope?​

Bean scope defines how many objects (instances) of a Spring Bean will be created and how long those objects will live.

In simple words:

Bean scope tells Spring whether to create one object, a new object every time, or an object for a particular request/session.

For example:

@Service
public class UserService {
}

Spring creates and manages this UserService object. The scope determines how Spring manages its instances.

1. Singleton Scope — Most Important​

Singleton is the default scope in Spring.

It means:

Only one instance of the bean is created per Spring IoC container.

Example:

@Service
@Scope("singleton")
public class UserService {
}

Suppose multiple classes need UserService:

Controller ──┐
│
Service ─────┼──→ Same UserService object
│
Other Class ─┘

2. Prototype Scope​

Spring creates a new bean instance every time the bean is requested from the container.

Example:

@Component
@Scope("prototype")
public class UserService {
}

Now:

UserService user1 = context.getBean(UserService.class);
UserService user2 = context.getBean(UserService.class);

Here:

user1 → UserService Object #1

user2 → UserService Object #2

Therefore:

user1 == user2

will be:

false

Simple difference

Singleton:

getBean() → Object #1
getBean() → Object #1
getBean() → Object #1

Prototype:

getBean() → Object #1
getBean() → Object #2
getBean() → Object #3

3. Request Scope​

Request scope is mainly used in web applications.

It means:

A new bean instance is created for each HTTP request.

Example:

@Component
@Scope("request")
public class RequestData {
}

Suppose these requests come:

Request 1 → RequestData #1

Request 2 → RequestData #2

Request 3 → RequestData #3

Each HTTP request gets its own object.

4. Session Scope​

Session scope means:

One bean instance is created for each HTTP session.

Example:

@Component
@Scope("session")
public class UserSession {
}

Suppose:

User A → Session A → UserSession #1

User B → Session B → UserSession #2

All requests from User A's session use the same UserSession object.

User A
│
├── Request 1 ──┐
├── Request 2 ──┼──→ UserSession #1
└── Request 3 ──┘

5. Application Scope​

One bean instance exists for the lifecycle of the web application/ServletContext.

Example:

@Component
@Scope("application")
public class ApplicationData {
}

It is associated with the application's ServletContext.

You can think of it as application-wide data.

6. WebSocket Scope​

WebSocket scope creates:

One bean instance for the lifecycle of a WebSocket session.

Example:

@Component
@Scope("websocket")
public class WebSocketData {
}

It's useful for applications that maintain WebSocket connections.

Spring Bean Scopes Matrix​

Spring supports six core scopes (four of which are available only in web-aware contexts):

ScopeLifetime & Instance CountThread-Safety Considerations
singleton (default)Exactly one instance per Spring IoC container. Reused everywhere.Must be stateless or thread-safe. Concurrent threads access same instance.
prototypeA brand new instance is created every time the bean is requested.Caller manages bean. Spring does not call @PreDestroy for prototype beans!
requestOne instance per HTTP request lifecycle.Thread-safe per request thread. Destroyed when request completes.
sessionOne instance per HTTP Session.Scoped to specific user session. Serialized if sessions replicated.
applicationOne instance per ServletContext.Shared across all servlets in container.
websocketOne instance per WebSocket lifecycle.Bound to WebSocket session.

What happens when Prototype is injected into Singleton?​

When a prototype bean is injected into a singleton bean, the prototype bean is instantiated only once at container bootstrap! To receive a new prototype instance each time, use @Lookup or ObjectProvider<T>:

@Service
public class OrderService {
private final ObjectProvider<ReportGenerator> reportGeneratorProvider;

public OrderService(ObjectProvider<ReportGenerator> provider) {
this.reportGeneratorProvider = provider;
}

public void generate() {
ReportGenerator generator = reportGeneratorProvider.getObject(); // Fresh prototype instance!
generator.process();
}
}

Is Singleton Bean Thread-Safe?​

No.

A singleton Bean means:

One instance is shared.

It does not automatically mean thread-safe.

Suppose:

@Service
public class CounterService {

private int count = 0;

public void increment() {
count++;
}
}

Multiple requests can access the same singleton instance concurrently.

Request 1 ──┐
Request 2 ──┼──→ Same CounterService instance
Request 3 ──┘

Complete Bean Lifecycle Hook Sequence​

1. Instantiation (new MyBean())
│
2. Populate Properties (Dependency Injection)
│
3. BeanNameAware / BeanFactoryAware / ApplicationContextAware
│
4. BeanPostProcessor: postProcessBeforeInitialization()
│
5. @PostConstruct Method
│
6. InitializingBean: afterPropertiesSet()
│
7. Custom init-method (@Bean(initMethod = "..."))
│
8. BeanPostProcessor: postProcessAfterInitialization() (Proxying happens here!)
│
[ BEAN IS ACTIVE AND READY TO SERVE REQUESTS ]
│
9. @PreDestroy Method (Upon context shutdown)
│
10. DisposableBean: destroy()
│
11. Custom destroy-method (@Bean(destroyMethod = "..."))

@PostConstruct​

@PostConstruct is used to run logic after dependency injection is complete and before the Bean is ready for normal use.

Example:

@Service
public class CustomerService {

@PostConstruct
public void initialize() {
System.out.println("CustomerService initialized");
}
}

Conceptually:

Create Bean
↓
Inject dependencies
↓
@PostConstruct
↓
Bean ready

It can be useful for initialization logic.

@PreDestroy​

@PreDestroy is used for cleanup before a managed Bean is destroyed.

Example:

@Service
public class ConnectionService {

@PreDestroy
public void cleanup() {
System.out.println("Cleaning resources");
}
}

Conceptually:

Application shutting down
↓
@PreDestroy
↓
Bean destroyed

BeanPostProcessor Real-World Power​

A BeanPostProcessor (BPP) can intercept and modify any bean instance before or after initialization:

@Component
public class CustomAuditPostProcessor implements BeanPostProcessor {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean.getClass().isAnnotationPresent(Audited.class)) {
System.out.println("Intercepted bean for auditing: " + beanName);
// Can wrap bean in dynamic proxy here
}
return bean;
}
}

Spring itself uses BeanPostProcessors to implement @Async, @Transactional, and @Scheduled.