Hello! In our previous lesson, we dove deep into using the @Query annotation to gain full control over data access, moving beyond convention to explicit configuration for complex and performance-critical database operations.
Today, we will take a similar "under the hood" look at a core concept of the Spring Framework itself. This lesson will help you understand how the components you've been building—services, repositories, controllers—are actually created, wired together, and managed.
This lesson addresses the learning outcome: Describe the Spring bean lifecycle from instantiation to destruction.
Understanding this lifecycle is crucial. It demystifies how Spring works, explains why certain annotations behave the way they do, and gives you the power to hook into this process to perform custom actions, such as initializing resources or cleaning up connections. For someone with your background in system design, this insight into the container's management process is fundamental to building robust and well-behaved applications.
We will cover:
- The high-level stages of a bean's life.
- The detailed sequence of events during bean creation and destruction.
- The different ways to define initialization and destruction logic, with a focus on modern best practices.
- The importance of gracefully shutting down the Spring container.
The Conceptual Lifecycle: Why We Need Custom Hooks
At its heart, the Spring IoC (Inversion of Control) container is responsible for managing the lifecycle of objects, which we call "beans." This process is more sophisticated than a simple new MyObject(). The container handles creating the object, injecting its dependencies, and finally, destroying it when it's no longer needed.
Often, you need to execute custom logic at specific points in this lifecycle. For example:
- After creation and dependency injection: You might need to open a network connection, read a configuration file, or pre-populate a cache. The bean isn't truly "ready" until this is done.
- Before destruction: You need a chance to release resources gracefully, such as closing file handles, shutting down a thread pool, or flushing pending data to a database.
The following video provides an excellent introduction to this concept and illustrates the basic stages of a bean's life.
Customizing Bean Nature in Spring | Bean Lifecycle, @PostConstruct & @PreDestroy Explained
The video 'Customizing Bean Nature in Spring' by CodeSnippet clearly explains the motivation for lifecycle callbacks and presents a simple, high-level view of the bean lifecycle.
Please watch the segment from 03:40 to 05:16. It walks through a clear diagram showing the main stages: Container Start -> Bean Creation -> Dependency Injection -> Bean Ready -> Bean Destruction -> Application Stop.
This gives us our basic mental model. Now, let's zoom in and look at the detailed sequence of events that occur within the "Bean Creation" and "Bean Destruction" phases.
A Detailed Look at the Bean Lifecycle Phases
The lifecycle of a Spring bean is a well-defined sequence of steps. The flowchart below provides a comprehensive map of this journey, from the initial instantiation to the final destruction. We will refer to this diagram as we break down each phase.

To complement the diagram, the following article provides a concise, text-based summary of the phases.
Hooking Into the Spring Bean Lifecycle - Reflectoring
The article 'Hooking Into the Spring Bean Lifecycle' from Reflectoring offers a very clear, structured breakdown of the creation and destruction phases.
Read the section titled 'The Spring Bean Lifecycle'. It cleanly separates the process into 'Bean Creation Phases' and 'Bean Destruction Phases'. Pay attention to the names of the different steps, as we will be discussing them next.
Bean Creation Sequence
Based on the diagram and article, here is the detailed sequence of events during bean creation:
- Instantiation: Spring creates an instance of the bean, typically by calling its constructor.
- Populating Properties: Spring resolves and injects the bean's dependencies. This is where field, constructor, or setter injection (e.g., using
@Autowired) happens. - Aware Interfaces: If the bean implements specific
Awareinterfaces, Spring calls their methods to provide the bean with access to parts of its container environment. Common examples include:BeanNameAware: Provides the bean with its own ID in the container.ApplicationContextAware: Gives the bean access to theApplicationContextitself.
BeanPostProcessor(Before Initialization): Before any custom initialization methods are called,BeanPostProcessors get their first chance to process the bean. This is a powerful mechanism for implementing cross-cutting concerns.- Initialization Callbacks: Spring invokes the bean's initialization methods. If a bean has multiple types of initialization callbacks defined, they are executed in this specific order:
- Method annotated with
@PostConstruct. afterPropertiesSet()method (if the bean implements theInitializingBeaninterface).- The custom
initMethodspecified in a@Beanannotation.
- Method annotated with
BeanPostProcessor(After Initialization):BeanPostProcessors get their second chance to process the bean. This is where frameworks often wrap the original bean in a proxy to add behaviors like transaction management or security.- Bean is Ready: The bean is now fully initialized and ready to be used by the application.
Bean Destruction Sequence
When the application shuts down and the Spring container is closed, the destruction process is initiated for singleton beans.
- Destruction Callbacks: Similar to initialization, if multiple destruction callbacks are defined, they are called in a specific order:
- Method annotated with
@PreDestroy. destroy()method (if the bean implements theDisposableBeaninterface).- The custom
destroyMethodspecified in a@Beanannotation.
- Method annotated with
- Bean is Destroyed: The bean is removed from the container.
Implementing Lifecycle Callbacks: Modern vs. Legacy
Now that you know the sequence, let's see how to implement these callbacks. While there are three primary methods, the modern, annotation-based approach is strongly recommended.
The Recommended Approach: @PostConstruct and @PreDestroy
The JSR-250 annotations @PostConstruct and @PreDestroy are the modern standard for defining lifecycle callbacks. They are clean, unintrusive, and don't tie your code to Spring-specific interfaces.
The CodeSnippet video we watched earlier demonstrates this approach perfectly.
Customizing Bean Nature in Spring | Bean Lifecycle, @PostConstruct & @PreDestroy Explained
Let's return to the 'Customizing Bean Nature in Spring' video to see a practical demonstration of the annotation-based approach.
Watch from 13:18 to 18:09. The video first explains why annotations are preferred over interfaces. It then refactors the code to use @PostConstruct and @PreDestroy, showing how they achieve the same result in a cleaner way.
As the video shows, you simply annotate any public, void, no-argument method with @PostConstruct or @PreDestroy.
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class ResourceManager {
@PostConstruct
public void initialize() {
System.out.println("ResourceManager: Initializing resources...");
// e.g., open file, connect to a remote service
}
public void useResources() {
System.out.println("ResourceManager: Using resources...");
}
@PreDestroy
public void cleanup() {
System.out.println("ResourceManager: Cleaning up resources...");
// e.g., close file, disconnect from service
}
}
Legacy Approaches
You may encounter two other approaches, especially in older projects or third-party libraries:
- Interface-based (
InitializingBean,DisposableBean): Your bean implements these Spring-specific interfaces and overrides theafterPropertiesSet()anddestroy()methods. This couples your component directly to the Spring Framework. - Method-based (
initMethod,destroyMethod): When defining a bean via a@Beanannotation in a@Configurationclass, you can specify the names of the init and destroy methods as string attributes. This is useful when you can't modify the source code of the class itself.
@Configuration
public class AppConfig {
@Bean(initMethod = "initialize", destroyMethod = "cleanup")
public LegacyResourceHandler legacyResourceHandler() {
return new LegacyResourceHandler();
}
}
The Shutdown Hook: Ensuring Destruction
A common point of confusion is when the @PreDestroy method doesn't seem to be called. In a standalone Java application, the JVM exits immediately after the main method completes. It doesn't wait for the Spring container to shut down gracefully.
To ensure your destruction callbacks are executed, you must register a shutdown hook with the application context. This tells the JVM to close the Spring container before the application exits.
The article below explains this critical, practical detail.
Spring Bean Lifecycle: Best Practices and Pitfalls - HowToDoInJava
The article 'Spring Bean Lifecycle: Best Practices and Pitfalls' from HowToDoInJava highlights a crucial step for ensuring destroy methods are called.
Please read section 8, 'Registering a Shutdown Hook is Necessary'. It shows how, without a shutdown hook, the destroy method is not called, and then provides the one-line solution: applicationContext.registerShutdownHook();.
In a Spring Boot application that runs continuously (like a web application), this is handled for you. But for command-line applications or simple tests, it's something you must manage yourself.
The Full Execution Order in Action
To put all this theory together, let's see the definitive order of execution with a concrete example that uses all the different types of callbacks.
Hooking Into the Spring Bean Lifecycle - Reflectoring
The Reflectoring article we saw earlier has an excellent final section that demonstrates the precise execution order.
Read the section 'The Execution Order'. It provides a Java class that implements all the lifecycle interfaces and annotations. The key part is the log output at the end, which shows the exact sequence in which each method is called. Compare this output with the lifecycle diagram from earlier.
As you can see from the logs in the article, the execution order is exactly as we described: BeanPostProcessor (before) -> @PostConstruct -> InitializingBean -> init-method -> BeanPostProcessor (after).
Conclusion
You now have a detailed map of the Spring bean lifecycle. This knowledge moves you from being just a user of the framework to an informed developer who understands how its core container operates.
Key Takeaways:
- The Spring container manages the full lifecycle of beans, from instantiation and dependency injection to initialization and eventual destruction.
- You can hook into this lifecycle to perform custom logic using annotations, interfaces, or configuration attributes.
- The modern, recommended approach is to use the
@PostConstructand@PreDestroyannotations, as they are part of a standard (JSR-250) and don't couple your code to Spring. - The order of initialization callbacks is:
@PostConstruct, thenInitializingBean.afterPropertiesSet(), then custominitMethod. - The order of destruction callbacks is:
@PreDestroy, thenDisposableBean.destroy(), then customdestroyMethod. - For non-web applications, you must register a shutdown hook to ensure destruction methods are called.
This theoretical foundation is essential. In our next lesson, "Use @PostConstruct and @PreDestroy to hook into bean lifecycle events," we will put this knowledge into practice with hands-on coding exercises. After that, we'll explore how the lifecycle differs for beans with different scopes, such as singleton versus prototype.