Spring Beans and Bean Lifecycle: A Simple Practical Guide
If you are learning Spring Boot, one concept you will hear constantly is Spring Bean.
At first, it sounds simple:
"A Spring Bean is an object managed by Spring."
But what does "managed" actually mean?
Who creates the object?
When are its dependencies injected?
When does initialization happen?
What happens when the application shuts down?
Let's understand these questions with a simple example.
What Is a Spring Bean?
A Spring Bean is an object that is created and managed by the Spring IoC container.
For example:
@Service
public class PaymentService {
public void pay() {
System.out.println("Payment completed");
}
}
Because PaymentService is annotated with @Service, Spring can discover it during component scanning and register it as a bean.
We can then inject it into another class:
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public void placeOrder() {
paymentService.pay();
System.out.println("Order placed");
}
}
Notice that we didn't write:
PaymentService paymentService = new PaymentService();
Spring creates the PaymentService bean and injects it into OrderService.
This is Dependency Injection.
Bean vs Normal Java Object
This distinction is important.
If we write:
PaymentService service = new PaymentService();
we have created a normal Java object.
Spring is not automatically managing it.
Therefore, Spring won't automatically manage its dependency injection or lifecycle callbacks.
But when Spring creates the object as a bean, Spring manages it.
A simple way to remember it:
Normal Java object
↓
Created by your code
↓
You manage it
Spring Bean
↓
Created/registered by Spring
↓
Spring manages it
This is one of the practical reasons we use Dependency Injection instead of creating dependencies manually with new.
How Does Spring Create a Bean?
For a class such as:
@Service
public class PaymentService {
}
Spring's simplified process looks like this:
Application starts
↓
Component scanning
↓
Spring discovers PaymentService
↓
Bean definition is created
↓
PaymentService is instantiated
↓
Dependencies are injected
↓
Initialization happens
↓
Bean is ready to use
The exact internal process is more detailed, but this simplified flow is enough to understand the main idea.
What Is Bean Lifecycle?
A Spring Bean has a lifecycle.
The simplified lifecycle is:
Bean Definition
↓
Instantiation
↓
Dependency Injection
↓
Initialization
↓
Bean Ready
↓
Application Running
↓
Application Shutdown
↓
Destruction
Let's look at the important stages.
1. Bean Instantiation
Spring creates an instance of the bean.
For example:
@Service
public class PaymentService {
}
Spring creates an instance of PaymentService.
Conceptually:
Spring Container
↓
PaymentService instance
2. Dependency Injection
Suppose OrderService depends on PaymentService:
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
Spring provides the PaymentService dependency when creating OrderService.
The important sequence is:
Create PaymentService
↓
PaymentService becomes available
↓
Create OrderService
↓
Inject PaymentService
This is why constructor injection is useful: the dependency is available when the object is constructed.
3. @PostConstruct
Sometimes a bean needs to perform some initialization after its dependencies have been injected.
We can use @PostConstruct:
@Service
public class PaymentService {
@PostConstruct
public void init() {
System.out.println("PaymentService initialized");
}
}
@PostConstruct runs after the bean has been created and its dependencies have been injected.
So the simplified flow becomes:
Instantiate
↓
Inject dependencies
↓
@PostConstruct
↓
Bean ready
For example, initialization could be used to load some required configuration or prepare resources.
4. Bean Is Ready
After initialization is complete, Spring considers the bean ready for normal use.
For example:
orderService.placeOrder();
At this point:
- The object has been created.
- Its dependencies have been injected.
- Initialization callbacks have completed.
The bean can now participate normally in the application.
5. Application Shutdown
What happens when the application stops?
For managed beans, Spring performs the appropriate destruction callbacks when the ApplicationContext is gracefully closed.
We can use @PreDestroy for cleanup:
@Service
public class PaymentService {
@PreDestroy
public void destroy() {
System.out.println("PaymentService destroyed");
}
}
The simplified shutdown flow is:
ApplicationContext closes
↓
Destruction callbacks
↓
@PreDestroy
↓
Bean destroyed
This can be useful when a bean needs to release resources or perform cleanup.
Dependency Order
There is another interesting part of the lifecycle.
Suppose:
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
Here:
OrderService
↓
PaymentService
OrderService depends on PaymentService.
Therefore, during startup, the dependency needs to be ready first:
PaymentService
↓
OrderService
During destruction, the order is reversed:
OrderService
↓
PaymentService
A simple mental model is:
Startup:
Dependency → Dependent
Shutdown:
Dependent → Dependency
A Complete Example
Let's put the important lifecycle callbacks together:
@Service
public class PaymentService {
@PostConstruct
public void init() {
System.out.println("Payment initialized");
}
public void pay() {
System.out.println("Payment completed");
}
@PreDestroy
public void destroy() {
System.out.println("Payment destroyed");
}
}
And:
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
@PostConstruct
public void init() {
System.out.println("Order initialized");
}
public void placeOrder() {
paymentService.pay();
System.out.println("Order placed");
}
@PreDestroy
public void destroy() {
System.out.println("Order destroyed");
}
}
The simplified lifecycle looks like:
Application starts
↓
PaymentService created
↓
PaymentService @PostConstruct
↓
OrderService created
↓
PaymentService injected
↓
OrderService @PostConstruct
↓
Application running
↓
Application shuts down
↓
OrderService @PreDestroy
↓
PaymentService @PreDestroy
↓
Beans destroyed
The Main Idea
You don't need to memorize a long list of Spring lifecycle methods.
Start with this mental model:
Spring Container
↓
Creates the Bean
↓
Injects Dependencies
↓
Initializes the Bean
↓
Bean Ready
↓
Application Runs
↓
Application Shutdown
↓
Destroys the Bean
And remember:
A Spring Bean is an object whose creation, dependencies, and lifecycle are managed by the Spring container.
Once this becomes clear, concepts like @Service, @Component, Dependency Injection, @PostConstruct, and @PreDestroy start fitting together naturally.
In a future article, we can take the next step and look at Spring Bean Scopes — especially Singleton vs Prototype — and see how they affect bean instances in a real application.
Top comments (0)