All articles
Spring CoreExam Tips

Spring Bean Lifecycle Explained: From Instantiation to Destruction

October 15, 20258 min read

Every Spring Bean goes through a defined lifecycle managed by the ApplicationContext. Understanding this lifecycle — especially the callback order — is one of the most frequently tested topics on the VMware Spring Professional exam (2V0-72.22).

Lifecycle Overview

The full lifecycle has 7 phases:

  1. Instantiation — the bean object is created
  2. Dependency Injection — properties and dependencies are set
  3. Aware Interfaces — Spring injects infrastructure references
  4. BeanPostProcessor — before initialization — cross-cutting logic runs before init callbacks
  5. Initialization Callbacks — custom init logic runs (@PostConstruct, afterPropertiesSet, init-method)
  6. BeanPostProcessor — after initialization — cross-cutting logic runs after init callbacks
  7. Bean is Ready — the bean serves requests until context shutdown triggers Destruction Callbacks

Phase 1: Instantiation

Spring creates the bean instance using one of:

  • Constructor with parameters — the most common approach in modern Spring (constructor injection)
  • A @Bean factory method in a @Configuration class
  • A no-arg constructor — when no dependencies need injection
@Component
public class OrderService {
 
    private final PaymentService paymentService;
 
    // Spring calls this constructor and injects PaymentService
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

For @Bean methods, Spring calls the method itself as a factory:

@Configuration
public class AppConfig {
 
    @Bean
    public UserService userService() {
        return new UserService(); // Spring calls this factory method
    }
}

Phase 2: Dependency Injection

After the bean is instantiated, Spring populates any remaining dependencies via:

  • Constructor injection (preferred — dependencies are set during instantiation)
  • Setter injection — via @Autowired on setter methods
  • Field injection — via @Autowired on fields (not recommended, makes testing harder)
@Component
public class NotificationService {
 
    private EmailSender emailSender;
 
    // Setter injection
    @Autowired
    public void setEmailSender(EmailSender emailSender) {
        this.emailSender = emailSender;
    }
}

Note: With constructor injection, phases 1 and 2 effectively happen together — dependencies are injected through the constructor at instantiation time. Setter and field injection happen as a separate step after the object is created.


Phase 3: Aware Interfaces

After dependency injection, Spring checks if the bean implements any Aware interfaces and injects the corresponding infrastructure objects. The following three are called directly by the container (invokeAwareMethods) in this order:

  1. BeanNameAware.setBeanName(String name) — receives the bean's name in the container
  2. BeanClassLoaderAware.setBeanClassLoader(ClassLoader cl) — receives the classloader
  3. BeanFactoryAware.setBeanFactory(BeanFactory factory) — receives the owning BeanFactory
@Component
public class AuditService implements BeanNameAware, BeanFactoryAware {
 
    private String beanName;
 
    @Override
    public void setBeanName(String name) {
        this.beanName = name; // e.g. "auditService"
    }
 
    @Override
    public void setBeanFactory(BeanFactory factory) {
        // Access to the owning BeanFactory
    }
}

Additional Aware interfaces — ApplicationContextAware, EnvironmentAware, ResourceLoaderAware, ApplicationEventPublisherAware, and MessageSourceAware — are handled by ApplicationContextAwareProcessor, which is a BeanPostProcessor. They are called during Phase 4 (BPP before initialization), not in this phase. The practical effect is the same — they still run before @PostConstruct — but the mechanism is different.

Exam tip: In modern Spring, prefer @Autowired or constructor injection to get the ApplicationContext instead of implementing ApplicationContextAware. The Aware interfaces exist for framework-level code or cases where injection is not possible.


Phase 4: BeanPostProcessor — Before Initialization

BeanPostProcessor (BPP) is a powerful extension point that lets you modify or wrap beans before and after their initialization callbacks. Spring calls postProcessBeforeInitialization() on every bean for each registered BPP.

@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
 
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("Before init: " + beanName);
        return bean; // return the (possibly modified) bean
    }
 
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("After init: " + beanName);
        return bean;
    }
}

@PostConstruct is processed by a BeanPostProcessor

This is a critical detail for the exam: @PostConstruct is not built into Spring's core initialization — it is handled by CommonAnnotationBeanPostProcessor, which is itself a BeanPostProcessor. During its postProcessBeforeInitialization() call, it detects and invokes @PostConstruct methods.

This means @PostConstruct runs during the BPP "before initialization" phase, before afterPropertiesSet() and custom init-methods.


Phase 5: Initialization Callbacks

After BPP pre-processing (including @PostConstruct), Spring calls the remaining initialization callbacks in this order:

1. InitializingBean.afterPropertiesSet()

@Component
public class CacheService implements InitializingBean {
 
    @Override
    public void afterPropertiesSet() {
        // Called by Spring after @PostConstruct
    }
}

2. Custom init-method

@Bean(initMethod = "setup")
public CacheService cacheService() {
    return new CacheService();
}

Exam tip: The full initialization order is: @PostConstruct (via BPP) → afterPropertiesSet() → custom init-method. All three are optional — you can use any combination.


Phase 6: BeanPostProcessor — After Initialization

After all init callbacks complete, Spring calls postProcessAfterInitialization() on each registered BPP. This is where Spring creates AOP proxies — wrapping the bean in a proxy that applies aspect advice.

Exam tip: This is why the @PostConstruct method itself cannot be intercepted by AOP advice — the proxy does not exist yet during initialization. The proxy is created in this phase, after init callbacks have already run.


Phase 7: Bean Is Ready → Destruction

The bean is now fully initialized and available in the ApplicationContext. For singleton-scoped beans, it remains in the container until shutdown.

When the ApplicationContext is closed — via context.close() or a JVM shutdown hook (context.registerShutdownHook()) — Spring calls destruction callbacks in this order:

1. @PreDestroy (recommended)

@Component
public class CacheService {
 
    @PreDestroy
    public void cleanup() {
        System.out.println("Clearing cache...");
    }
}

2. DisposableBean.destroy()

@Component
public class CacheService implements DisposableBean {
 
    @Override
    public void destroy() {
        // Called by Spring on context shutdown
    }
}

3. Custom destroy-method

@Bean(destroyMethod = "shutdown")
public CacheService cacheService() {
    return new CacheService();
}

Important: Destruction callbacks are not called for prototype-scoped beans. Spring does not track prototype instances after creation. See our Bean Scopes guide for details.


Complete Callback Order

This is the full sequence that Spring follows for each singleton bean — memorize it for the exam:

StepCallbackMechanism
1InstantiationConstructor or factory method
2Dependency injection@Autowired, setter, or field injection
3setBeanName()BeanNameAware
4setBeanClassLoader()BeanClassLoaderAware
5setBeanFactory()BeanFactoryAware
6postProcessBeforeInitialization()BeanPostProcessor (includes @PostConstruct via CommonAnnotationBeanPostProcessor and setApplicationContext() via ApplicationContextAwareProcessor)
7afterPropertiesSet()InitializingBean
8Custom init-method@Bean(initMethod = "...")
9postProcessAfterInitialization()BeanPostProcessor (AOP proxies created here)
10Bean is ready
11@PreDestroyCommonAnnotationBeanPostProcessor (via DestructionAwareBeanPostProcessor)
12destroy()DisposableBean
13Custom destroy-method@Bean(destroyMethod = "...")

Exam Quick Reference

Q: Which initialization callback runs first — @PostConstruct or afterPropertiesSet()? @PostConstruct runs first. It is invoked by CommonAnnotationBeanPostProcessor during the BPP postProcessBeforeInitialization phase, before afterPropertiesSet().

Q: Do destruction callbacks run for prototype-scoped beans? No. Spring does not manage the full lifecycle of prototype beans. After returning a prototype instance, Spring does not call destroy callbacks on it.

Q: What triggers the destruction phase? Calling context.close() or registering a JVM shutdown hook via context.registerShutdownHook().

Q: Where in the lifecycle are AOP proxies created? During BeanPostProcessor.postProcessAfterInitialization() — after all init callbacks have completed. This is why @PostConstruct methods cannot be intercepted by AOP advice.

Q: What is the role of BeanPostProcessor? BPP is a container extension point that can modify or wrap any bean before and after initialization. Spring uses BPPs internally — for example, CommonAnnotationBeanPostProcessor processes @PostConstruct and @PreDestroy, and AutowiredAnnotationBeanPostProcessor handles @Autowired.


Frequently Asked Questions

What is the Spring Bean lifecycle?

The Spring Bean lifecycle is the sequence of steps Spring follows when creating, configuring, and destroying a bean. It includes instantiation, dependency injection, Aware interface callbacks, BeanPostProcessor hooks, initialization callbacks (@PostConstruct, afterPropertiesSet, init-method), and destruction callbacks (@PreDestroy, destroy, destroy-method). Understanding this order is essential for the VMware Spring Professional exam.

What is the difference between @PostConstruct and afterPropertiesSet()?

Both are initialization callbacks, but they differ in mechanism and priority. @PostConstruct is a standard Java annotation (JSR-250) processed by CommonAnnotationBeanPostProcessor — it runs first. afterPropertiesSet() requires implementing the InitializingBean interface and runs second. @PostConstruct is preferred because it does not couple your code to the Spring API.

Why does @PreDestroy not work on prototype beans?

Spring does not track prototype bean instances after creation. Once a prototype bean is returned to the caller, Spring no longer holds a reference to it. Without a reference, Spring cannot invoke destruction callbacks when the context shuts down. You must manage cleanup of prototype beans manually.

Can @PostConstruct be intercepted by Spring AOP?

No. AOP proxies are created during BeanPostProcessor.postProcessAfterInitialization(), which runs after @PostConstruct. At the time @PostConstruct executes, the bean is not yet wrapped in a proxy, so AOP advice does not apply to initialization methods.

Test your lifecycle knowledge with practice questions — 10 exam-style questions covering bean lifecycle, scopes, and more

Spring Bean Scopes explained — how scope affects lifecycle and destruction behavior

Spring AOP & Pointcut guide — understand where AOP fits in the lifecycle

Get Full Access — 970+ Questions — full practice exam bank with detailed explanations

Practice This Topic

Reinforce what you've learned with free practice questions and detailed explanations.