Appearance
Spring Boot: Bean and its Lifecycle | Inversion of Control (IoC)
Real World Analogy
Think of a restaurant kitchen. The kitchen manager (IoC container) is responsible for hiring staff (creating beans), assigning them to stations (dependency injection), making sure they are ready before service starts (post construct), putting them to work during service (bean in use), and finally releasing them at closing time (pre destroy). You as a developer are like the restaurant owner: you describe what you need and the kitchen manager handles all the logistics.
A bean in Spring Boot is simply a Java object whose entire lifecycle, from creation to destruction, is managed by the Spring IoC container. You do not call new yourself; the container does it for you.
What Is a Bean?
A bean is a Java object managed by the Spring IoC container. The IoC container (also called the Application Context) is responsible for:
- Discovering which classes need to become beans
- Constructing those objects
- Injecting dependencies
- Calling lifecycle callbacks
- Destroying beans when the application shuts down
The key insight is that you give up manual object creation in favour of letting Spring manage everything. This is the core idea behind Inversion of Control: instead of your code controlling when and how objects are made, that control is inverted to the framework.
Two Ways to Create a Bean
1. @Component Annotation (Convention over Configuration)
When you annotate a class with @Component, you are telling Spring: "Create and manage an object of this class using your default rules."
java
import org.springframework.stereotype.Component;
@Component
public class UserService {
private String username;
private String email;
// Default constructor — Spring calls this automatically
public UserService() {
System.out.println("Initializing UserService");
}
// Getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}Spring uses its autoconfiguration to call the default (no argument) constructor. No external instructions needed — hence "convention over configuration."
@Controller, @Service, and @Repository are all specialisations of @Component. They also instruct Spring to create and manage beans, while additionally signalling the role of the class.
The Problem with Custom Constructors
What if your class does not have a default constructor?
java
@Component
public class UserService {
private String username;
private String email;
// Custom constructor — no default constructor present
public UserService(String username, String email) {
this.username = username;
this.email = email;
}
}When Spring tries to create this bean using autoconfiguration, it looks for a no argument constructor. Finding none, and not knowing what values to pass to the custom constructor, the application fails to start.
2. @Bean Annotation (External Configuration)
Use @Bean when you need to tell Spring exactly how to construct an object, especially when no default constructor exists or when the object comes from a third party library.
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public UserService createUserBean() {
// We explicitly tell Spring how to construct this object
return new UserService("defaultUsername", "default@email.com");
}
}@Configuration tells Spring: "This class contains bean definitions. Scan it for @Bean methods."
Priority rule: If you annotate a class with both @Component and provide an @Bean method in a configuration class, Spring gives priority to the @Bean configuration.
Multiple beans of the same type: If you write two @Bean methods both returning UserService, Spring creates two separate bean instances. You can distinguish them later using @Qualifier or by providing a bean name.
How Spring Finds Your Beans
Spring needs to scan your code to find all classes that should become beans. It does this in two ways:
1. @ComponentScan
java
@SpringBootApplication
@ComponentScan(basePackages = "com.example.myapp")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}@ComponentScan tells Spring to look in the specified package and all its sub packages for classes annotated with @Component, @Service, @Repository, @Controller, etc.
Important: @SpringBootApplication already includes @ComponentScan internally. By default it scans starting from the package where your main class lives. You only need to add @ComponentScan explicitly if you want to scan a different package.
2. @Configuration Classes
Spring also looks for @Configuration classes (which are themselves @Component instances internally) and registers every @Bean method it finds inside them.
When Are Beans Created?
There are two initialisation strategies:
Eager Initialisation
Beans are created at application startup. This is the default behaviour for singleton beans. The moment you run the application, Spring scans for beans and creates them all before serving any request.
Lazy Initialisation
Beans are created only when first needed. You opt into this with @Lazy:
java
@Component
@Lazy
public class HeavyReportGenerator {
public HeavyReportGenerator() {
System.out.println("HeavyReportGenerator created");
}
}This bean will not be created at startup. It will only be instantiated the first time another bean or piece of code requests it.
The Complete Bean Lifecycle
Here is the full sequence of events that every bean goes through:
Application Starts
│
▼
1. IoC Container Initialises
│
▼
2. Bean Discovery (via @ComponentScan and @Configuration)
│
▼
3. Bean Construction (constructor is called)
│
▼
4. Dependency Injection (@Autowired fields/constructors resolved)
│
▼
5. @PostConstruct Method Called
│
▼
6. Bean Is in Use (your application logic runs)
│
▼
7. @PreDestroy Method Called
│
▼
8. Bean Destroyed / IoC Container ClosedLet us walk through each step with code.
Step 1 and 2: Container Starts and Discovers Beans
When you start a Spring Boot application, you will see log lines like:
Initializing Spring embedded WebApplicationContext
Started Application in 2.4 secondsThe WebApplicationContext is the concrete implementation of the IoC container. At this point, Spring runs component scanning and @Configuration processing to build its list of beans to create.
Step 3: Bean Construction
java
@Component
public class UserService {
public UserService() {
System.out.println("Step 3: UserService bean constructed");
}
}For singleton beans, this happens once at startup. For lazy or prototype beans, it happens on first use.
Step 4: Dependency Injection
java
@Component
public class UserService {
private final OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService;
System.out.println("Step 4: Dependency injected into UserService");
}
}After the bean is constructed, Spring resolves all @Autowired dependencies and injects them. If a dependency is marked @Lazy, Spring creates it at this point (because it is now needed).
Step 5: @PostConstruct
java
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class UserService {
private final OrderService orderService;
private Map<String, Object> cache;
public UserService(OrderService orderService) {
this.orderService = orderService;
}
@PostConstruct
public void init() {
// Bean is fully constructed AND dependencies are injected
// Safe to use dependencies here
System.out.println("Step 5: @PostConstruct — bean fully ready");
this.cache = new HashMap<>();
cache.put("defaultKey", "defaultValue");
}
}Use @PostConstruct for initialisation logic that depends on injected fields (e.g., pre loading a cache, opening a connection pool, setting up initial data). It runs once, right after dependency injection completes.
Step 6: Bean Is in Use
Your application logic runs. APIs are served, methods are called. The bean does its work.
Step 7: @PreDestroy
java
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class DatabaseConnectionPool {
@PostConstruct
public void openConnections() {
System.out.println("Opening DB connections");
}
@PreDestroy
public void closeConnections() {
// Release resources before the bean is destroyed
System.out.println("Step 7: @PreDestroy — closing DB connections");
}
}Use @PreDestroy to release resources: close database connections, flush pending writes, cancel scheduled tasks, etc.
Step 8: Bean Destroyed
The IoC container closes. All managed beans are destroyed.
Complete Working Example
java
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
// Dependency bean
@Component
public class OrderService {
public OrderService() {
System.out.println("[OrderService] Constructed");
}
public void processOrder() {
System.out.println("[OrderService] Processing order");
}
}
// Main bean demonstrating the full lifecycle
@Component
public class UserService {
private final OrderService orderService;
@Autowired
public UserService(OrderService orderService) {
this.orderService = orderService;
System.out.println("[UserService] Constructed with OrderService injected");
}
@PostConstruct
public void onReady() {
System.out.println("[UserService] @PostConstruct — ready to serve");
}
public void serve() {
orderService.processOrder();
}
@PreDestroy
public void onShutdown() {
System.out.println("[UserService] @PreDestroy — cleaning up");
}
}
// Main class
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(Application.class, args);
// Explicitly closing to trigger @PreDestroy
ctx.close();
}
}Expected output:
[OrderService] Constructed
[UserService] Constructed with OrderService injected
[UserService] @PostConstruct — ready to serve
[UserService] @PreDestroy — cleaning upEager vs Lazy: Practical Demonstration
java
@Component
public class EagerBean {
public EagerBean() {
// Called at application startup
System.out.println("EagerBean created at startup");
}
}
@Component
@Lazy
public class LazyBean {
public LazyBean() {
// Called only when first used
System.out.println("LazyBean created on first use");
}
}When you run the application, you will see EagerBean created at startup immediately. LazyBean created on first use only appears when some code first requests the LazyBean from the container.
Summary
| Concept | Description |
|---|---|
| Bean | A Java object managed by the Spring IoC container |
| IoC Container | The engine that creates, injects, and destroys beans |
@Component | Marks a class for automatic bean creation |
@Bean | Provides manual configuration for bean creation |
@ComponentScan | Tells Spring which packages to scan for beans |
@PostConstruct | Called after dependency injection, before the bean is used |
@PreDestroy | Called before the bean is destroyed |
| Eager Init | Bean created at application startup (default for singletons) |
| Lazy Init | Bean created only when first requested |
Interview Questions
Q1. What is a Spring Bean? A Spring bean is a Java object that is instantiated, configured, and managed by the Spring IoC (Inversion of Control) container. The container controls the full lifecycle of the object.
Q2. What is Inversion of Control? Inversion of Control is a design principle where the control of object creation and dependency wiring is transferred from application code to a framework or container. Instead of writing new MyService(), you declare your needs and the container wires everything together.
Q3. What is the difference between @Component and @Bean?@Component is a class level annotation that tells Spring to automatically detect and create a bean using its default constructor. @Bean is a method level annotation inside a @Configuration class that provides explicit, manual instructions on how to create the bean. Use @Bean when you need control over construction (e.g., third party classes, custom constructors, conditional setup).
Q4. What is the order of bean lifecycle events? Container starts → bean discovery → bean construction → dependency injection → @PostConstruct → bean in use → @PreDestroy → bean destroyed.
Q5. When would you use @PostConstruct? When you need to run initialisation logic that requires injected dependencies to already be available. Examples: pre loading caches, initialising connection pools, setting up default data.
Q6. What is the difference between eager and lazy initialisation? Eager initialisation means the bean is created at application startup. Lazy initialisation (via @Lazy) means the bean is created only when first requested. Singleton beans are eager by default. Prototype beans are lazy by default.
Q7. What happens if a class has a custom constructor and you annotate it with @Component? Spring tries to invoke the no argument constructor by default. If no such constructor exists, the application will fail to start with a bean creation exception. You must either provide a default constructor or switch to @Bean configuration where you explicitly pass the required arguments.
Q8. What is the difference between @ComponentScan and @Configuration?@ComponentScan tells Spring which packages to scan for stereotype annotations like @Component. @Configuration marks a class as a source of explicit bean definitions via @Bean methods. Both are used during bean discovery; @SpringBootApplication includes component scanning by default starting from the main class package.