Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 35

The Spring Bean Lifecycle

Recommended resources

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.

Spring Bean Lifecycle Flowchart
This flowchart illustrates the complete lifecycle of a Spring bean. It details the sequence from instantiation and property population, through various awareness interfaces and post-processors, to initialization callbacks (`@PostConstruct`, `afterPropertiesSet`, `init-method`), and finally through the destruction callbacks (`@PreDestroy`, `destroy`, `destroy-method`).

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:

  1. Instantiation: Spring creates an instance of the bean, typically by calling its constructor.
  2. Populating Properties: Spring resolves and injects the bean's dependencies. This is where field, constructor, or setter injection (e.g., using @Autowired) happens.
  3. Aware Interfaces: If the bean implements specific Aware interfaces, 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 the ApplicationContext itself.
  4. 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.
  5. 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:
    1. Method annotated with @PostConstruct.
    2. afterPropertiesSet() method (if the bean implements the InitializingBean interface).
    3. The custom initMethod specified in a @Bean annotation.
  6. 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.
  7. 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.

  1. Destruction Callbacks: Similar to initialization, if multiple destruction callbacks are defined, they are called in a specific order:
    1. Method annotated with @PreDestroy.
    2. destroy() method (if the bean implements the DisposableBean interface).
    3. The custom destroyMethod specified in a @Bean annotation.
  2. 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:

  1. Interface-based (InitializingBean, DisposableBean): Your bean implements these Spring-specific interfaces and overrides the afterPropertiesSet() and destroy() methods. This couples your component directly to the Spring Framework.
  2. Method-based (initMethod, destroyMethod): When defining a bean via a @Bean annotation in a @Configuration class, 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 @PostConstruct and @PreDestroy annotations, as they are part of a standard (JSR-250) and don't couple your code to Spring.
  • The order of initialization callbacks is: @PostConstruct, then InitializingBean.afterPropertiesSet(), then custom initMethod.
  • The order of destruction callbacks is: @PreDestroy, then DisposableBean.destroy(), then custom destroyMethod.
  • 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.