What is Bean Lifecycle
The Bean Lifecycle is the deterministic sequence of phases that an object traverses under the management of the Spring Inversion of Control (IoC) Container: from reading configu...
🟢 Junior Level
1. What is the Bean Lifecycle?
The Bean Lifecycle is the deterministic sequence of phases that an object traverses under the management of the Spring Inversion of Control (IoC) Container: from reading configuration metadata (BeanDefinition), allocating memory in the JVM heap, and injecting collaborators, to executing initialization callbacks, servicing runtime requests, and gracefully releasing resources upon container shutdown.
Bean Lifecycle Progression:
1. Instantiation ──► Allocates raw object in heap via constructor
│
▼
2. Populate Properties ──► Injects dependencies (@Autowired fields and setters)
│
▼
3. Initialization ──► Executes @PostConstruct, afterPropertiesSet, init-method
│
▼
4. In Service / Ready ──► Fully assembled bean services application traffic
│
▼
5. Destruction ──► Executes @PreDestroy, DisposableBean, destroy-method
[!IMPORTANT] Jakarta EE Migration (Spring Boot 3+ / Spring Framework 6+): Lifecycle annotations migrated from
javax.annotation.*tojakarta.annotation.*:
@PostConstruct$\to$jakarta.annotation.PostConstruct@PreDestroy$\to$jakarta.annotation.PreDestroy
2. Basic Lifecycle Example in Spring Boot 3
package com.example.service;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Service;
@Service
public class PaymentProcessingService {
private final PaymentGatewayClient gatewayClient;
// 1. Instantiation: Container invokes constructor and injects dependency
public PaymentProcessingService(PaymentGatewayClient gatewayClient) {
this.gatewayClient = gatewayClient;
System.out.println("1. Instantiation: Object created in heap");
}
// 3. Initialization: Called AFTER all dependencies are fully wired
@PostConstruct
public void init() {
System.out.println("3. Initialization: Pre-warming connections & validating credentials");
gatewayClient.ping();
}
// 4. In Service: Business logic
public void processPayment(double amount) {
gatewayClient.charge(amount);
}
// 5. Destruction: Called during ApplicationContext shutdown
@PreDestroy
public void cleanup() {
System.out.println("5. Destruction: Closing network sockets & releasing buffers");
gatewayClient.disconnect();
}
}
🟡 Middle Level
1. Detailed Lifecycle Execution Pipeline
Inside the Spring container (AbstractAutowireCapableBeanFactory), the complete lifecycle unfolds across strict, fine-grained phases:
[ BeanDefinition Parsing ] (Reads XML, @Component, @Bean metadata)
│
▼
[ BeanFactoryPostProcessor ] (Modifies metadata BEFORE any bean is created)
│
▼
[ Instantiation ] (Constructor chosen; raw instance created in heap)
│
▼
[ Populate Properties ] (AutowiredAnnotationBeanPostProcessor injects fields & setters)
│
▼
[ Aware Interfaces ] (BeanNameAware, BeanFactoryAware, ApplicationContextAware)
│
▼
[ BPP: postProcessBeforeInitialization ] (@PostConstruct via CommonAnnotationBeanPostProcessor)
│
▼
[ InitializingBean: afterPropertiesSet() ]
│
▼
[ Custom init-method ] (@Bean(initMethod = "customInit"))
│
▼
[ BPP: postProcessAfterInitialization ] (Wraps bean in AOP Dynamic Proxy / CGLIB)
│
▼
[ IN SERVICE / READY TO USE ]
│
▼
[ Destruction Phase ] (@PreDestroy ──► DisposableBean.destroy() ──► custom destroy-method)
2. Aware Interfaces
If a bean requires access to internal Spring infrastructure, it implements marker Aware interfaces. Spring injects these infrastructure references in a strict sequence right before initialization:
BeanNameAware: Supplies the bean’s registered string ID in the container.BeanClassLoaderAware: Supplies theClassLoaderused by the container.BeanFactoryAware: Supplies the low-levelBeanFactoryinstance.EnvironmentAware: Supplies active profiles and environment properties.ApplicationContextAware: Supplies the high-levelApplicationContext(injected viaApplicationContextAwareProcessor).
3. Singleton vs Prototype Scope Lifecycle Invariant
The lifecycle behavior of Spring beans differs drastically depending on their declared scope:
| Lifecycle Stage | singleton Scope |
prototype Scope |
|---|---|---|
| Creation | Once at context startup (unless @Lazy) |
On every getBean() call or injection |
| Dependency Injection | Wired once | Wired every time a new instance is created |
@PostConstruct |
Executed once by container | Executed for every new instance |
| Container Reference | Retained permanently in singleton cache | Discarded immediately after initialization! |
@PreDestroy Hook |
Executed on context shutdown | ❌ NEVER EXECUTED BY SPRING! |
[!CAUTION] Prototype Memory & Resource Leak Hazard: Spring completely forgets about a prototype bean immediately after handing it to the caller. The container does not track prototype destruction and will never execute
@PreDestroyorDisposableBean.destroy()!If a prototype bean acquires file handles, database connections, or background threads, the client code must explicitly invoke destruction methods:
PrototypeWorker worker = context.getBean(PrototypeWorker.class); try { worker.doWork(); } finally { worker.cleanup(); // Must be manually cleaned up by client! }
4. Global Subsystem Coordination: SmartLifecycle
When an application needs to start or stop entire infrastructure subsystems (e.g., Kafka listeners, Netty servers, WebSocket endpoints) in a coordinated order after all beans and proxies are fully assembled, it implements SmartLifecycle:
@Component
public class KafkaConsumerCoordinator implements SmartLifecycle {
private volatile boolean running = false;
@Override
public void start() {
this.running = true;
System.out.println("SmartLifecycle: Kafka listener started AFTER all proxies initialized");
}
@Override
public void stop() {
this.running = false;
System.out.println("SmartLifecycle: Kafka listener gracefully stopped");
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
return 100; // Lower numbers start earlier and stop later
}
@Override
public boolean isAutoStartup() {
return true;
}
}
🔴 Senior Level
1. Internal Engine: AbstractAutowireCapableBeanFactory.doCreateBean
In OpenJDK Spring Framework, the core bean manufacturing pipeline is implemented inside AbstractAutowireCapableBeanFactory.doCreateBean():
// Conceptual flow inside doCreateBean():
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. Instantiation:
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 2. Early Singleton Exposure (Resolving circular references via 3-level cache):
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 3. Property Population:
populateBean(beanName, mbd, instanceWrapper);
// 4. Initialization & AOP Proxy Generation:
Object exposedObject = initializeBean(beanName, bean, mbd);
return exposedObject;
}
Detailed Breakdown:
createBeanInstance: Resolves constructors and instantiates the raw object viaConstructor.newInstance(). The object exists in heap, but non-constructor fields arenull.addSingletonFactory: Registers anObjectFactorylambda in the 3rd level cache (singletonFactories) so that circular collaborators can obtain an early reference.populateBean: CallsInstantiationAwareBeanPostProcessor.postProcessProperties()to inject fields and setters via reflection.initializeBean:- Calls Aware interfaces (
BeanNameAware,BeanFactoryAware). - Executes
postProcessBeforeInitialization(@PostConstruct). - Calls
InitializingBean.afterPropertiesSet()and custominit-method. - Executes
postProcessAfterInitialization: AOP Dynamic Proxies (JDK/CGLIB) are generated here byAbstractAutoProxyCreator.
- Calls Aware interfaces (
2. The 3-Level Singleton Cache Architecture
To resolve circular dependencies without deadlocks during field/setter injection, DefaultSingletonBeanRegistry uses three synchronized maps:
DefaultSingletonBeanRegistry Internal Tiers:
├── Level 1: singletonObjects (ConcurrentHashMap<String, Object>)
│ └── Ready-to-use, fully initialized, and proxied singleton beans.
│
├── Level 2: earlySingletonObjects (HashMap<String, Object>)
│ └── Unpopulated raw or early-proxied references exposed to break cycles.
│
└── Level 3: singletonFactories (HashMap<String, ObjectFactory<?>>)
└── Factories generating early references/proxies only when circularity is detected.
The Lookup Sequence in getSingleton(beanName, allowEarlyReference):
- Check
singletonObjects(Level 1). Found? Return it. - Not found and bean is currently in creation? Check
earlySingletonObjects(Level 2). Found? Return it. - Not found? Synchronize on
singletonObjectslock. Check Level 2 again. - Retrieve
ObjectFactoryfromsingletonFactories(Level 3), invokefactory.getObject(), store inearlySingletonObjects, and remove fromsingletonFactories.
[!NOTE] Why Constructor Injection Cannot Use the 3-Level Cache: To register an entry in
singletonFactories, the object must first be allocated in the heap. In Constructor Injection, Thread A blocks insidecreateBeanInstance(A)waiting for parameter $B$, while Thread B blocks insidecreateBeanInstance(B)waiting for parameter $A$. Neither object can be allocated, triggering an immediateBeanCurrentlyInCreationException.
3. The Fatal @PostConstruct AOP Proxy Trap
A very common architectural bug is placing @Transactional or @Async on a method called inside @PostConstruct:
@Service
public class CurrencyService {
@PostConstruct
public void init() {
// ❌ CRITICAL BUG: Transaction will NOT open!
loadInitialRates();
}
@Transactional
public void loadInitialRates() {
// Database write operations executed WITHOUT an active transaction!
}
}
Why This Fails:
Lifecycle Sequence:
1. Object Instantiation (Raw instance created)
2. Dependency Population (Fields wired)
3. @PostConstruct executed ──► Operates on raw `this` (Target Object)!
4. postProcessAfterInitialization ──► AOP Dynamic Proxy wraps Target Object!
At the moment @PostConstruct executes, the AOP Dynamic Proxy does not exist yet! The call executes directly on the raw target instance, bypassing the TransactionInterceptor.
The Production-Grade Solution:
Use an application event listener that fires after the container has fully initialized all beans and proxies:
@Service
public class CurrencyService {
@EventListener(ApplicationReadyEvent.class) // ✅ Fires AFTER all proxies are active!
@Transactional
public void onApplicationReady() {
loadInitialRates(); // Runs inside a fully active database transaction!
}
public void loadInitialRates() { ... }
}
4. Java Memory Model (JMM) Safe Publication Guarantees
Spring guarantees thread safety when multiple worker threads read singleton beans:
- Constructor Injection: Fields declared
finalbenefit from JMM §17.5 freeze action guarantees, ensuring immediate visibility across CPU cores without memory barriers. - Setter / Field Injection: Although fields are non-final, Spring publishes the completed singleton into
singletonObjectsinside asynchronized (this.singletonObjects)block, and worker threads read fromConcurrentHashMap. This establishes a formal happens-before edge under the JMM, ensuring safe publication.
4 Tricky Questions
1. Why do @Transactional and @Async fail to function when called from inside a @PostConstruct method?
Answer:
AOP dynamic proxies (which intercept method invocations to manage transactions or asynchronous thread dispatch) are created during the very last step of initialization: inside BeanPostProcessor.postProcessAfterInitialization().
The @PostConstruct hook runs earlier in the lifecycle, inside postProcessBeforeInitialization(). When @PostConstruct runs, the bean is still a raw, unproxied Java instance (this). Invoking another method on this executes direct bytecode calls on the unproxied instance, completely bypassing Spring’s TransactionInterceptor and AsyncExecutionInterceptor.
Remedy: Place startup transactional logic inside an event listener listening for ApplicationReadyEvent or ContextRefreshedEvent.
2. What happens to a @Scope("prototype") bean when the ApplicationContext is closed, and why?
Answer:
Nothing happens. Spring does not call @PreDestroy or DisposableBean.destroy() on prototype-scoped beans.
Spring’s design contract states that the container creates, configures, wires, and initializes prototype beans, and then immediately surrenders ownership to the calling client. If Spring retained references to all prototype beans to track their destruction, the container would quickly suffer catastrophic memory leaks. Therefore, managing the destruction and resource cleanup of prototype beans is strictly the responsibility of the client code that requested them.
3. Why does the 3-level singleton cache resolve circular dependencies for field injection, but fails fast for constructor injection?
Answer:
For field injection, Spring first calls the default constructor to allocate a raw, empty instance in heap memory. It immediately wraps this raw instance in an ObjectFactory and places it into the 3rd level cache (singletonFactories). When collaborator Bean $B$ is created, it retrieves this early reference, completes its own lifecycle, and allows Bean $A$ to finish.
With Constructor Injection, an object cannot be instantiated in heap memory without its constructor parameters being satisfied first. Spring gets blocked during createBeanInstance(A) because parameter $B$ is missing, and blocked during createBeanInstance(B) because parameter $A$ is missing. Because neither instance can be allocated in heap, no early reference can be published to singletonFactories, causing Spring to throw a fail-fast BeanCurrentlyInCreationException.
4. What is the fundamental difference between BeanFactoryPostProcessor (BFPP) and BeanPostProcessor (BPP)?
Answer:
BeanFactoryPostProcessor(BFPP) operates onBeanDefinitionmetadata before any bean instances are created in heap memory. It can modify bean scopes, change property values, or resolve placeholders (e.g.,PropertySourcesPlaceholderConfigurerresolving${db.url}).BeanPostProcessor(BPP) operates on actual physical Java bean instances in heap memory after they have been instantiated. Its methods (postProcessBeforeInitializationandpostProcessAfterInitialization) run for every bean, handling@Autowiredinjection,@PostConstructexecution, and wrapping beans into AOP dynamic proxies.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“The Spring Bean Lifecycle represents the complete progression of a managed bean across five macro-phases:
- BeanDefinition Parsing & BFPP (Metadata configuration).
- Instantiation (Raw object created via constructor).
- Property Population (Fields and setters injected via BPP).
- Initialization (Aware callbacks $\to$
@PostConstruct$\to$InitializingBean$\to$ custominit-method$\to$ AOP Dynamic Proxy creation).- Destruction (
@PreDestroy$\to$DisposableBean$\to$ customdestroy-method).Proxies are created after
@PostConstruct, which is why@Transactionaldoes not work inside@PostConstruct. Forprototypebeans, Spring never calls destruction callbacks.”
Red Flags & Pitfalls (DO NOT Say)
- ❌ “In the constructor,
@Autowiredfields are already populated.” (Fields arenullinside the constructor unless passed via constructor injection). - ❌ “Spring calls
@PreDestroyfor prototype beans when they are garbage collected.” (Spring never calls destruction hooks on prototype beans). - ❌ “You can execute transactional database updates inside
@PostConstruct.” (AOP proxies are created after@PostConstruct; transactions will not open). - ❌ “Instantiation and initialization are the same phase.” (Instantiation creates the raw instance in heap; initialization runs after all dependencies are injected).
Complete Phase Progression Checklist
Spring Bean Lifecycle Progression Table:
┌───────┬──────────────────────────────────────────┬────────────────────────────────────────┐
│ Phase │ Hook / Operation │ Internal Actor / Mechanism │
├───────┼──────────────────────────────────────────┼────────────────────────────────────────┤
│ Meta │ BeanDefinition Metadata Parsing │ ClassPathBeanDefinitionScanner (ASM) │
│ Meta │ BeanFactoryPostProcessor │ PropertySourcesPlaceholderConfigurer │
│ 1 │ Constructor Selection & Instantiation │ Constructor.newInstance() / CGLIB │
│ 2 │ Early Singleton Factory Exposure │ DefaultSingletonBeanRegistry (Level 3) │
│ 3 │ Property Population (Field/Setter DI) │ AutowiredAnnotationBeanPostProcessor │
│ 4 │ Aware Interfaces Injected │ BeanNameAware, ApplicationContextAware │
│ 5 │ @PostConstruct Callback │ CommonAnnotationBeanPostProcessor │
│ 6 │ InitializingBean.afterPropertiesSet() │ Direct interface invocation │
│ 7 │ Custom init-method │ Configured via @Bean(initMethod = "…") │
│ 8 │ AOP Proxy Generation │ AbstractAutoProxyCreator (BPP After) │
│ READY │ Bean In Service │ Servicing application traffic │
│ 9 │ @PreDestroy Callback │ InitDestroyAnnotationBeanPostProcessor │
│ 10 │ DisposableBean.destroy() │ Direct interface invocation │
│ 11 │ Custom destroy-method │ Configured via @Bean(destroyMethod) │
└───────┴──────────────────────────────────────────┴────────────────────────────────────────┘