Describe the Spring bean lifecycle and the callback hooks available (@PostConstruct, @PreDestroy, InitializingBean, etc.).

9 minadvancedbean-lifecyclepostconstructpredestroy

Quick Answer

A singleton bean's lifecycle runs: instantiation, dependency injection (constructor/setter/field), then any Aware interface callbacks (BeanNameAware, ApplicationContextAware), then BeanPostProcessor 'before initialization', then initialization callbacks (@PostConstruct, then InitializingBean.afterPropertiesSet(), then a custom init-method), then BeanPostProcessor 'after initialization' (where proxies like AOP advice are typically applied), and the bean is ready for use — until container shutdown triggers destruction callbacks (@PreDestroy, DisposableBean.destroy(), or a custom destroy-method).

Detailed Answer

For a singleton bean, the full lifecycle (simplified) runs in this order:

  1. Instantiation — Spring calls the bean's constructor (resolving constructor-injected dependencies at this point).
  2. Dependency injection — setter and field injection (@Autowired on setters/fields) happens after construction.
  3. Aware interface callbacks — if the bean implements marker interfaces like BeanNameAware, BeanFactoryAware, or ApplicationContextAware, Spring calls their respective setter methods, giving the bean access to its own name, the factory, or the full context.
  4. BeanPostProcessor.postProcessBeforeInitialization() — runs for every bean, for every registered BeanPostProcessor (a Spring extension point most application code never implements directly, but many Spring features are built on it internally).
  5. Initialization callbacks, in this order:
    • @PostConstruct-annotated method (JSR-250, the most common modern choice).
    • InitializingBean.afterPropertiesSet() (if the bean implements that interface — an older, interface-based alternative).
    • A custom init-method specified via @Bean(initMethod = "...") or XML.
  6. BeanPostProcessor.postProcessAfterInitialization() — this is where Spring applies AOP proxying: if the bean needs a proxy (for @Transactional, @Async, custom aspects, etc.), the proxy is created and returned here, meaning the object other beans actually get injected may be a proxy wrapping the real instance.
  7. Bean is ready — used by the application for its normal lifetime.
  8. Destruction (on container shutdown), in a similar layered order: @PreDestroy, then DisposableBean.destroy(), then a custom destroy-method.
@Component
class CacheWarmer implements InitializingBean, DisposableBean {
    @PostConstruct
    void warmUp() { /* runs first among init callbacks */ }

    @Override
    public void afterPropertiesSet() { /* runs after @PostConstruct */ }

    @PreDestroy
    void logShutdown() { /* runs before destroy() */ }

    @Override
    public void destroy() { /* release resources */ }
}

Practical guidance: prefer @PostConstruct/@PreDestroy (simple, annotation-based, no coupling to Spring-specific interfaces) over implementing InitializingBean/DisposableBean directly, unless you're writing framework-level code that specifically needs the interface-based contract.