Hello! Welcome back to our course on the Spring Framework.
In the previous lesson, we took a deep dive into the theory of the Spring bean lifecycle. We mapped the entire journey of a bean from instantiation to destruction and identified three different ways to hook into this process. We concluded that using the @PostConstruct and @PreDestroy annotations is the modern, recommended best practice because it keeps your code clean and decoupled from the framework.
Today, we're moving from theory to practice. This lesson focuses on the learning outcome: Use @PostConstruct and @PreDestroy to hook into bean lifecycle events. We will write code to implement these lifecycle callbacks, allowing our components to manage their own resources reliably. For a software architect, mastering this pattern is key to building resilient and well-behaved systems that correctly initialize and clean up resources like network connections, file handles, or thread pools.
Introduction: From Theory to Practice
Let's start by seeing these annotations in action. The previous lesson's flowchart showed that @PostConstruct runs after dependency injection is complete, and @PreDestroy runs just before the bean is removed from the container.
The following video demonstrates this sequence with a simple, practical example. It will show you the basic syntax and how Spring automatically invokes these methods for you.
Spring Bean Lifecycle | Spring Core Part 7 | Spring Boot Tutorial | Spring Framework
The video 'Spring Bean Lifecycle' from TShaped Skills provides a clear demonstration of adding @PostConstruct and @PreDestroy to a bean and observing the console output to see when they are executed.
Please watch from 06:19 to 10:35. The instructor adds methods annotated with @PostConstruct and @PreDestroy to a simple Greeting bean and then runs the application to show the full execution order in the console.
As you saw, you don't need to call these init and destroy methods yourself. By adding the annotations, you instruct the Spring container to invoke them at the correct points in the bean's lifecycle.
Common Use Cases and Best Practices
While printing to the console is useful for learning, the real power of these annotations lies in managing resources and state. Let's explore some professional use cases and best practices.
The following article is written from the perspective of an experienced developer and provides excellent, practical advice that should resonate with your background in system design.
Spring @PostConstruct and @PreDestroy With Practical Examples – TheLinuxCode
The article 'Spring @PostConstruct and @PreDestroy With Practical Examples' from TheLinuxCode provides a superb overview of how to use these annotations effectively in real-world applications.
Please read the following sections: '@PostConstruct: what it guarantees and what it doesn’t': This section lists excellent use cases like validating configuration and preparing caches. '@PreDestroy: what gets called, and when it doesn’t': This covers shutdown sequences. 'A minimal Spring Boot example you can run': A simple, practical code example. 'Practical scenario: initializing and shutting down an executor': A more advanced example showing how to manage a thread pool, which is a classic use case.
To summarize the key takeaways from the article, here are some of the most common and valuable uses for these lifecycle hooks:
- Resource Management: Opening and closing database connections, network sockets, or file handles.
- Configuration Validation: Checking that required properties (like URLs or API keys) have been injected and are valid before the bean is used.
- Cache Initialization: Pre-populating an in-memory cache with data from a database or another service at startup (often called "cache warming").
- Background Task Management: Starting and gracefully stopping background threads or scheduled tasks managed by an
ExecutorService.
Let's Build: A Resource-Managing Bean
Now, let's put this into practice by building a component that simulates a client connecting to an external service.
Imagine we have a ReportServiceClient bean whose job is to communicate with a remote reporting service. To do this, it must establish a connection when it starts and close the connection when the application shuts down. @PostConstruct and @PreDestroy are perfect for this.
Here is the code for a simple Spring Boot application that demonstrates this pattern. You can create a new Spring Boot project (using Spring Initializr) and replace the contents of your main application file with this code to run it yourself.
1. Dependency for Annotations
If you are using Java 9+ and Spring Boot 3+, the @PostConstruct and @PreDestroy annotations are in the jakarta.annotation-api library. Maven projects will need this dependency in pom.xml:
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
Spring Initializr typically includes this for you.
2. The ReportServiceClient Bean
This component manages a simulated network connection.
package com.example.lifecycle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class ReportServiceClient {
private boolean isConnected = false;
@PostConstruct
public void connect() {
System.out.println("Connecting to the remote report service...");
// Simulate establishing a network connection
this.isConnected = true;
System.out.println("Connection established.");
}
public void generateReport(String reportName) {
if (!isConnected) {
throw new IllegalStateException("Cannot generate report. Client is not connected.");
}
System.out.println("Generating report: " + reportName);
// Logic to communicate with the service would go here
}
@PreDestroy
public void disconnect() {
System.out.println("Disconnecting from the remote report service...");
// Simulate closing the network connection
this.isConnected = false;
System.out.println("Connection closed.");
}
}
3. The Main Application Class
This class will get the bean from the context, use it, and then close the context to trigger the destruction lifecycle.
package com.example.lifecycle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class LifecycleApplication {
public static void main(String[] args) {
// SpringApplication.run returns the ApplicationContext
ConfigurableApplicationContext context = SpringApplication.run(LifecycleApplication.class, args);
// Get the bean from the context
ReportServiceClient client = context.getBean(ReportServiceClient.class);
// Use the bean
client.generateReport("Monthly_Sales_Q4.pdf");
// Close the context to trigger the shutdown process
context.close();
}
}
When you run this application, you will see the following output, perfectly demonstrating the lifecycle in action:
Connecting to the remote report service...
Connection established.
Generating report: Monthly_Sales_Q4.pdf
... (Spring Boot shutdown logs) ...
Disconnecting from the remote report service...
Connection closed.
Notice how connect() is called automatically before we use the bean, and disconnect() is called automatically when we close the application context. This guarantees that our component is always in a valid state and its resources are cleaned up properly.
Critical Caveats for Robust System Design
As with any powerful tool, there are important caveats to be aware of to avoid common pitfalls. The article you read touches on these, but they are worth emphasizing from a system design perspective.
Spring @PostConstruct and @PreDestroy With Practical Examples – TheLinuxCode
Let's revisit 'TheLinuxCode' article to focus on the common mistakes and how bean scope affects the lifecycle.
Please read the sections: 'Common mistakes I keep seeing (and how to avoid them)': Focus on points 1 ('Doing heavy work in @PostConstruct') and 2 ('Forgetting about prototype scope'). 'Bean scope and lifecycle: singletons, prototypes, and beyond': This explains why the prototype scope behaves differently.
Here are the two most critical takeaways for building production-grade systems:
-
Keep Lifecycle Methods Fast: Your
@PostConstructmethod blocks application startup. If it performs a heavy task like downloading a large file or running a slow database query, it will increase your application's startup time. This is particularly problematic in containerized environments (like Kubernetes) or serverless platforms where fast startup is crucial. The best practice is to keep initialization logic lightweight (e.g., validation, configuration) and move heavy work to an asynchronous task that runs after startup is complete. -
@PreDestroyis Not Called for Prototype Beans: The lifecycle we've discussed applies to singleton beans (the default scope). For prototype beans, Spring's responsibility ends after it creates the bean and gives it to you. The container does not manage the full lifecycle of prototype beans, and therefore will not call the@PreDestroymethod. If you have a prototype-scoped bean that holds resources (like a connection), you are responsible for manually cleaning it up. This is a very common source of resource leaks in Spring applications.
Conclusion
In this lesson, you have moved from understanding the theory of the bean lifecycle to its practical application. You can now use @PostConstruct and @PreDestroy to create components that are self-contained, reliable, and manage their own resources effectively.
Key Takeaways:
@PostConstructis the ideal place to perform initialization tasks like opening connections, validating configuration, or warming up caches. It ensures the bean is fully ready before it's used.@PreDestroyis the corresponding hook for cleanup tasks, ensuring that resources are released gracefully when the application shuts down.- To ensure
@PreDestroyis called in a command-line application, you must explicitly close theApplicationContext. - For robust, production-ready systems, always keep lifecycle methods fast and be aware that
@PreDestroyhooks are not automatically called for prototype-scoped beans.
This last point about bean scopes is fundamentally important. Our next lesson, "Configure and compare the singleton and prototype bean scopes," will explore this topic in detail. You will learn what these scopes are, why you would choose one over the other, and how that choice impacts the lifecycle and behavior of your beans.