Spring Bean Lifecycle Explained
Fast recall
Quick revision
Core answer
Understanding of bean lifecycle hooks for proper initialization and cleanup logic
Using the constructor for logic that depends on injected fields — injection happens after construction
Explain why @PostConstruct is needed and give a concrete use case like opening database connections
Interview-ready guide
Interview answer
A singleton service is constructed after its constructor dependencies are resolved. Spring then populates setter/field properties, invokes relevant aware interfaces, runs registered post-processors and initialization callbacks, and may expose an AOP proxy. On normal context shutdown, @PreDestroy or other destruction callbacks run for managed singleton scopes. A hard process kill may prevent cleanup.
For example, a connection-owning client can validate its configuration in @PostConstruct and close its executor in @PreDestroy. Destruction callbacks apply to managed lifecycle beans, not automatically to every prototype instance.
Spring Bean Lifecycle Explained
Direct Answer
A common singleton lifecycle is definition -> instantiation -> dependency population -> aware callbacks -> BeanPostProcessor before-initialization -> @PostConstruct/other init callbacks -> after-initialization (possibly a proxy) -> use -> destruction callbacks when the context closes. Constructor injection happens during instantiation rather than in a later generic property-injection step. Scope and custom post-processors change the details.
Code Example
@Service
public class ReportService {
private final DataSource dataSource;
private Connection connection;
// Step 1: Constructor called (dependency injection happens here)
public ReportService(DataSource dataSource) {
this.dataSource = dataSource;
System.out.println("Constructor: dataSource injected");
}
// Step 2: After dependency injection, before bean is used
@PostConstruct
public void initialize() throws SQLException {
// Now dataSource is guaranteed to be non-null
this.connection = dataSource.getConnection();
System.out.println("PostConstruct: connection opened");
}
public List<Report> generateReports() {
// Bean is ready — use connection for queries
return List.of();
}
// Step 3: Before container shutdown
@PreDestroy
public void cleanup() throws SQLException {
if (connection != null && !connection.isClosed()) {
connection.close();
System.out.println("PreDestroy: connection closed");
}
}
}
Common Mistakes & Pitfalls
- Using constructor for dependency-dependent logic: Freshers often initialize caches or open connections in the constructor. This fails because injected fields are null until after construction. Always use @PostConstruct for logic that needs injected dependencies.
- Forgetting @PreDestroy cleanup: Not closing resources leads to connection leaks, file handle exhaustion, or thread pools that keep the JVM alive after shutdown. If you open it in @PostConstruct, close it in @PreDestroy.
- Assuming lifecycle for prototype beans: Spring doesn't call @PreDestroy on prototype-scoped beans. You must manage cleanup manually. Freshers often expect Spring to destroy prototypes automatically, causing resource leaks.
- Blocking in @PostConstruct: Long-running initialization (like warming a large cache) blocks application startup. Consider async initialization with @Async or lazy loading strategies instead.
Check your understanding
Answer these without looking back at the article.
- Why should initialization code go in @PostConstruct rather than the constructor?
- What is InitializingBean and DisposableBean?
- When is @PreDestroy not called?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.