Skip to main content

Bean Lifecycle in Spring Framework

Bean Lifecycle in Spring Framework: Complete Deep Dive

The Bean Lifecycle is arguably the most important architectural topic in Spring Core. It explains precisely how a bean is created, initialized, utilized, and eventually destroyed inside the Inversion of Control (IOC) Container.

Most developers can recite a basic summary:

Instantiate Bean → Inject Dependencies → @PostConstruct → Bean Ready → @PreDestroy

1. What is the Bean Lifecycle?

Standard Definition

The Bean Lifecycle is the complete chronological journey of a Spring Bean from its metadata parsing to its destruction inside the Spring IOC Container.

High-Level Initialization Flow

SpringApplication.run() | V Create ApplicationContext | V Component Scan | V Create BeanDefinition | V Instantiate Bean | V Inject Dependencies | V Initialize Bean | V Store in IOC Container | V Bean Ready | V Application Shutdown | V Destroy Bean

2. The Complete Initialization Sequence (Step-by-Step)

Phase 1: Bean Definition Creation

When Spring performs a @ComponentScan, it does NOT create the object immediately in memory. First, it generates an internal metadata blueprint.

If Spring finds @Service public class EmployeeService {}, it creates a BeanDefinition containing:

  • Bean Name: employeeService
  • Class: EmployeeService
  • Scope: singleton
  • Lazy: false
  • Dependencies: (Extracted from constructor)

Internal Flow: @ComponentClassPathBeanDefinitionScannerBeanDefinitionBeanDefinitionRegistry.

Phase 2: Bean Instantiation

Spring now generates the actual raw Java object in memory using the Reflection API.

Class<?> clazz = EmployeeService.class; Object bean = clazz.getDeclaredConstructor().newInstance();

This is the very first lifecycle callback—the Constructor Execution.

Phase 3: Dependency Injection

Spring analyzes the newly instantiated object to see what it requires. If EmployeeService requires an EmployeeRepository, the container searches its registry, initializes the repository (if necessary), and injects it.

Internal Engine: AutowiredAnnotationBeanPostProcessor handles the resolution and injection of @Autowired and constructor dependencies.

Phase 4: Aware Interfaces (Crucial for Senior Interviews)

After dependencies are injected, Spring checks if the bean implements any special "Aware" interfaces. These interfaces allow the bean to receive underlying framework infrastructure objects.

  • BeanNameAware: Executes setBeanName() so the bean knows its own registered name.
  • BeanFactoryAware: Executes setBeanFactory() to provide direct access to the factory.
  • ApplicationContextAware: Executes setApplicationContext() to provide access to the full container context.

Phase 5: BeanPostProcessor (Before Initialization)

Before any custom initialization logic runs, Spring executes a crucial interceptor interface across all beans: the BeanPostProcessor.

Specifically, it triggers the postProcessBeforeInitialization() method. A core internal processor here is the CommonAnnotationBeanPostProcessor, which is physically responsible for invoking the @PostConstruct annotation.

Phase 6: Initialization Callbacks

Once the framework setup is complete, Spring executes user-defined initialization logic in a strict order:

  1. @PostConstruct: Executed first. Used for cache loading, database warming, or API client setup.
  2. InitializingBean.afterPropertiesSet(): Executed second if the bean implements the InitializingBean interface.
  3. Custom Init Method: Executed last if you specified @Bean(initMethod = "start") in a configuration class.

Phase 7: BeanPostProcessor (After Initialization) & Proxy Creation

Spring triggers postProcessAfterInitialization(). This is a massively important phase.

If your bean requires transactional boundaries (@Transactional), asynchronous execution (@Async), caching (@Cacheable), or Spring Security, this is exactly where Spring generates the AOP Proxy wrapper around your raw bean.

Phase 8: Bean Ready & Singleton Cache

The fully initialized (and potentially proxied) bean is finally ready to serve traffic. It is moved into the framework's primary singleton cache.

Internal Cache: Stored inside the singletonObjects map managed by the DefaultSingletonBeanRegistry.


3. The Singleton Creation Cache Pattern

By default, Spring beans are @Scope("singleton"). This means only ONE object exists in the entire JVM memory space for that definition.

When multiple controllers request the EmployeeService via @Autowired, they are all handed the exact same memory reference (System.identityHashCode(service) proves this).

The Three-Level Cache (Advanced Concept)

To resolve Circular Dependencies (e.g., Bean A needs Bean B, but Bean B needs Bean A), Spring maintains a highly sophisticated three-tiered cache system:

  • Level 1: singletonObjects → Fully initialized, ready-to-use beans.
  • Level 2: earlySingletonObjects → Partially initialized beans (raw objects waiting on dependencies).
  • Level 3: singletonFactories → Object factories capable of generating early references (used to build AOP proxies early if needed).

Resolution Flow: singletonFactoriesearlySingletonObjectssingletonObjects.


4. The Destruction Lifecycle

When the application begins shutting down (e.g., context.close() or a JVM termination signal), Spring orchestrates a graceful shutdown of all singleton beans to prevent data corruption or orphaned connections.

Destruction Callbacks

Just like initialization, destruction logic runs in a strict sequential order:

  1. @PreDestroy: Executed first. Primarily used to close open files, release network resources, flush database connections, or stop background threads.
  2. DisposableBean.destroy(): Executed second if the bean implements the DisposableBean interface.
  3. Custom Destroy Method: Executed last if specified via @Bean(destroyMethod = "stop").

5. Master Interview Checklist

The Complete Ordered Sequence (Memorize This)

1. BeanDefinition Created 2. Instantiate Bean (Constructor Invocation) 3. Dependency Injection 4. BeanNameAware (setBeanName) 5. BeanFactoryAware (setBeanFactory) 6. ApplicationContextAware (setApplicationContext) 7. BeanPostProcessor (Before Initialization) 8. @PostConstruct 9. InitializingBean (afterPropertiesSet) 10. Custom Init Method (@Bean initMethod) 11. BeanPostProcessor (After Initialization / AOP Proxy Creation) 12. Bean Ready (Stored in singletonObjects) -- Application Execution Runtime -- 13. @PreDestroy 14. DisposableBean (destroy) 15. Custom Destroy Method (@Bean destroyMethod)

Top Interview Q&A

Q: Explain the Bean Lifecycle in summary.
A: It begins with BeanDefinition metadata generation, proceeds to raw instantiation via reflection, and handles dependency injection. It then triggers aware interfaces and initialization callbacks (@PostConstruct, afterPropertiesSet). Finally, post-processors wrap the bean in AOP proxies if required, storing it in the singleton cache for runtime use until the container shuts down and triggers destruction callbacks (@PreDestroy).

Q: Where exactly are Singleton Beans physically stored?
A: They are cached inside a map called singletonObjects, managed internally by the DefaultSingletonBeanRegistry class.

Q: Why is @PostConstruct necessary if we have a Constructor?
A: The constructor executes *before* dependencies are injected. If your initialization logic (like fetching initial data from a database) requires an injected repository, attempting to call it in the constructor will result in a NullPointerException. @PostConstruct guarantees that all dependencies have been successfully wired before executing the logic.

Q: When exactly are AOP Proxies created?
A: AOP proxies (which enable annotations like @Transactional, @Async, and @Cacheable) are typically created during the final initialization phase inside BeanPostProcessor.postProcessAfterInitialization().

Comments

Popular posts from this blog

How I Got Selected in MNC

Virtusa Sometimes success does not come from having the best coding skills or the perfect roadmap. Sometimes it comes from simply refusing to quit. This is the honest story of how I transitioned from a confused, rejected fresher to getting selected as an Associate Engineer at Virtusa. The Beginning: Confused About My Future After completing my graduation, I stared blankly at my career options. Like many freshers, I lacked a clear direction. Should I join a Java course? Should I prepare on my own? Should I just wait for campus placement opportunities? One day, I called my friend Chetan. He suggested I join Naresh i Technologies and start learning Java seriously. Still unsure of my path, I told him I needed time to think about it. A couple of days later, my phone buzzed with a WhatsApp message offering a job opportunity. They asked me to come for the next round of the recruitment process. Excitement completely took over. I packed my bags, traveled to th...

Spring Boot Introduction

Spring Boot Introduction: Architecture, Dependencies, and Embedded Servers Modern enterprise applications demand rapid development, frictionless deployment, and absolute minimal configuration. Before Spring Boot arrived, developers utilizing the Spring Framework wasted immense amounts of time configuring XML files, managing clashing dependencies, setting up clunky application servers, and stitching various Spring modules together manually. To eliminate these bottlenecks, Pivotal introduced Spring Boot . Built entirely on top of the traditional Spring Framework, Spring Boot is an "opinionated" framework. It aggressively simplifies application development by injecting auto-configuration, packaging starter dependencies, and embedding web servers directly into your application. This allows backend developers to focus entirely on building business logic rather than wrestling with infrastructure setup. What is Spring Boot? Spring Boot is a powerful extens...

Strings in C

C Programming: Working with Strings Unlike modern programming languages like Python or Java, C does not possess a dedicated "String" data type. Instead, C treats a string as a simple 1D array of characters. Real-life example: Think of a freight train. The train does not exist as one solid object; it consists of individual boxcars linked together. Similarly, a C string links individual characters side-by-side in memory. To let the computer know the train has ended, C attaches a special "caboose" called the Null Terminator ( \0 ). 1. Essential String Functions Handling strings manually requires complex loops. To save time, C provides a built-in library called <string.h> that contains powerful functions to manipulate text. strlen(): You use this to find the exact length of a string. The compiler counts the characters until it hits the \0 terminator. It does not count the terminator itself. strcpy(): You ...