Spring Bean Lifecycle
Spring Bean Lifecycle
Why this matters in interviews
Bean lifecycle questions are a litmus test for Spring depth. Candidates who only know @Autowired get stumped when asked what happens between container startup and a bean being ready to use, or why @PostConstruct is preferred over a constructor for initialisation logic. This topic also links directly to common production issues: unreleased resources, incomplete initialisation, and shutdown hooks.
Concept
Spring manages the complete lifecycle of every bean in its container. The sequence for a singleton bean:
1. Instantiation — constructor called
2. Property injection — @Autowired, @Value, setters applied
3. BeanNameAware — setBeanName(name) if implemented
4. BeanFactoryAware — setBeanFactory(bf) if implemented
5. ApplicationContextAware — setApplicationContext(ctx) if implemented
6. BeanPostProcessor — postProcessBeforeInitialization() for all beans
7. @PostConstruct — your init logic (JSR-250, preferred)
8. InitializingBean — afterPropertiesSet() if implemented
9. @Bean(initMethod) — custom init method if declared
10. BeanPostProcessor — postProcessAfterInitialization() (AOP proxies created here)
──── Bean in use ────
11. @PreDestroy — your cleanup logic
12. DisposableBean — destroy() if implemented
13. @Bean(destroyMethod) — custom destroy method if declared
Why not put init logic in the constructor?
At constructor time, Spring has not yet injected @Autowired fields. Using them in the constructor causes NPE.
@Service
public class CacheService {
@Autowired
private DataSource dataSource; // null at constructor time!
public CacheService() {
this.dataSource.getConnection(); // NullPointerException!
}
@PostConstruct
public void init() {
this.dataSource.getConnection(); // safe — injection is complete
}
}
BeanPostProcessor
BeanPostProcessor is how Spring's own infrastructure (AOP, @Transactional, @Async) wraps beans in proxies. Every bean passes through all registered BeanPostProcessors.
@Component
public class TimingPostProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean ready: " + beanName);
return bean; // must return the (possibly wrapped) bean
}
}
@Scope and lifecycle
- Singleton (default): created once, destroyed at context close
- Prototype: new instance per
getBean()call; Spring does not call@PreDestroyon prototypes - Request/Session: one instance per HTTP request/session (web context only)
Key rules / gotchas
@PostConstructis preferred overInitializingBean(Spring coupling) or constructor logic (no injection yet).- Spring does not manage
@PreDestroyon prototype beans. The caller is responsible for cleanup. BeanPostProcessorbeans are special — they are instantiated earlier than normal beans and cannot use@Autowiredon other beans that themselves require post-processing (circular post-processor issue).@Bean(destroyMethod = "")disables auto-detection of destroy methods — useful for third-party beans like connection pools that have their own shutdown hooks.ApplicationContext.close()triggers the shutdown lifecycle. Without it (e.g., in a non-web app withoutSpringApplication.run),@PreDestroymay never fire.SmartLifecyclegives fine-grained control over start/stop ordering across multiple beans — used in message listeners, scheduled tasks, etc.
Code example
// Simulates Spring bean lifecycle phases without a Spring context.
public class JavaLabRunner {
interface InitializingBean { void afterPropertiesSet() throws Exception; }
interface DisposableBean { void destroy() throws Exception; }
static class DatabasePool implements InitializingBean, DisposableBean {
private String url;
private boolean initialized = false;
// Simulates @Value injection
void setUrl(String url) { this.url = url; }
// Simulates @PostConstruct / afterPropertiesSet
@Override public void afterPropertiesSet() {
System.out.println("1. afterPropertiesSet: opening pool to " + url);
initialized = true;
}
void query(String sql) {
if (!initialized) throw new IllegalStateException("Bean not initialized!");
System.out.println("2. Executing: " + sql);
}
// Simulates @PreDestroy / destroy
@Override public void destroy() {
System.out.println("3. destroy: closing pool");
initialized = false;
}
}
// Simulates BeanPostProcessor wrapping
static <T> T applyPostProcessor(T bean, String name) {
System.out.println("[BPP] postProcessAfterInitialization: " + name);
return bean; // in real Spring, may return a proxy here
}
public static void main(String[] args) throws Exception {
System.out.println("Phase 1: instantiate");
DatabasePool pool = new DatabasePool();
System.out.println("Phase 2: inject properties");
pool.setUrl("jdbc:postgresql://localhost:5432/mydb");
System.out.println("Phase 3: BeanPostProcessor (before init)");
// [BPP] postProcessBeforeInitialization would run here
System.out.println("Phase 4: @PostConstruct / afterPropertiesSet");
pool.afterPropertiesSet();
System.out.println("Phase 5: BeanPostProcessor (after init)");
pool = applyPostProcessor(pool, "databasePool");
System.out.println("Phase 6: bean in use");
pool.query("SELECT * FROM users");
pool.query("SELECT COUNT(*) FROM orders");
System.out.println("Phase 7: @PreDestroy / destroy");
pool.destroy();
}
}
Interview questions you should be able to answer
-
Q: Why is
@PostConstructpreferred over constructor logic for initialisation?At constructor time,
@Autowireddependencies have not been injected yet — using them causes NPE.@PostConstructruns after full injection, making it the right place to open connections, warm caches, or validate configuration. -
Q: When does Spring call
@PreDestroy?On singleton beans when the
ApplicationContextis closed (either viactx.close()or a JVM shutdown hook registered by Spring Boot). Prototype-scoped beans never receive@PreDestroy— Spring does not track them after creation. -
Q: What does a
BeanPostProcessordo and when is it used?It intercepts every bean during initialisation and can return the original bean or a proxy. Spring uses it internally for AOP (
@Transactional,@Async),@Scheduled, validation, and more. Running afterpostProcessAfterInitializationis where AOP proxies are created. -
Q: What is the difference between
@PostConstructandInitializingBean.afterPropertiesSet()?Both run at the same lifecycle point.
@PostConstructis a JSR-250 standard annotation with no Spring coupling — preferred for portability and readability.afterPropertiesSet()is Spring-specific and requires implementing theInitializingBeaninterface. -
Q: How do you control the start/stop order of multiple beans?
Implement
SmartLifecycleand overridegetPhase()— beans with lower phase numbers start first and stop last. Alternatively, use@DependsOn("beanName")to declare a startup dependency between beans.
Further reading
Click Run to execute the code.