In this article, you will learn the Spring bean lifecycle and different ways to run bean initialization callbacks and bean destruction callbacks. Bean lifecycle simply means you want to execute callbacks before the spring bean is available to use and similarly execute callbacks before the bean is destroyed.

There are several ways to configure the Spring bean lifecycle callbacks as listed below.

  1. The JSR-250 specification using @PostConstruct and @PreDestroy annotations. This is the recommended way.
  2. Using the lifecycle callbacks in <bean/> or @bean declarations.
  3. Using the default initialization and destroy methods.
  4. Startup and Shutdown callbacks from the Lifecycle interface.

It is recommended to use JSR-250 @PostConstruct and @PreDestroy annotations as a best practice for executing lifecycle callbacks in a Spring application.

If you are using JDK 9 or higher, please make sure you add the javax.annotation-api dependency for JSR-250 to work.

pom.xml
 <!-- Use the recent version shown below -->
 <dependency>
     <groupId>javax.annotation</groupId>
     <artifactId>javax.annotation-api</artifactId>
     <version>1.3.2</version>
 </dependency>

For later version, 1.3.3 onwards use jakarta.annotation-api instead of javax.annotation-api.

pom.xml
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>1.3.5</version>
</dependency>
Maven repo

Spring Bean creation and destruction phases

Before exploring the ways to invoke lifecycle callbacks, it is important to understand the different phases a Spring Bean goes through before it is ready to use and before it is destroyed.

The below diagram shows the different stages spring bean goes through before it is ready to use. If you carefully observe the diagram below, the last 3 phases are mentioned in the above discussion about several ways to specify bean lifecycle callbacks.

Spring bean Lifecycle creation stages
Spring Bean Creation Phases

Similarly, the image below shows the different stages Spring bean goes through before the IoC shuts it down. I have listed all the 3 phases in the above list.

Spring bean Lifecycle destruction stages
Spring Bean Destruction Phases

1. Use @PostConstruct and @PreDestroy annotations – (JSR-250)

The JSR-250 specification provides 2 annotations, @PostConstruct and @PreDestroy. If you want logic to be executed before the bean is ready to use by the programs, use @PostCostruct. Similarly, if you want the callback to be executed before the bean gets destroyed, use @PreDestroy. These annotations are used on the method level. The CommonAnnotationBeanPostProcessor in Spring is responsible for the recognition of these lifecycle annotations.

The example below demonstrates the working of these annotations.

JsrExampleBean.java
package bean.jsr.callbacks;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class JsrExampleBean {

  @PostConstruct
  public void initCacheData() {
    System.out.println("Your custom initialization code goes here...");
  }

  @PreDestroy
  public void clearCacheData() {
    System.out.println("Your custom destroy code goes here...");
  }
}
JsrExampleConfig.java
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("bean.jsr.callbacks")
public class JsrExampleConfig {
  //Extra config goes
}
Java
TestJsrExample.java
package bean.jsr.callbacks;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestJsrExample {
  public static void main(String[] args) {

    ConfigurableApplicationContext context
        = new AnnotationConfigApplicationContext(JsrExampleConfig.class);
    context.getBean(JsrExampleBean.class);

    //This is important to shutdown gracefully
    context.registerShutdownHook();
  }
}

Output:

Your custom initialization code goes here...
Your custom destroy code goes here...

Note the way ApplicationContext is shutdown gracefully in a non-web spring application by invoking ConfigurableApplicationContext.registerShutdownHook();.

2. Using the Initialization and Destruction callbacks in bean config

Spring uses BeanPostProcessor implementations to process callback interfaces and the appropriate methods.

You can configure them in init-method and destroy-method of bean configuration. Assign the custom initialization callback to the init-method property of bean definitions and the custom destruction callback to destroy-method.

The below code demonstrates how the custom initialization and destruction callbacks are configured. It represents the annotation-based java configurations.

BLCConfigClass.java
package bean.lifecycle.callbacks;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("bean.lifecycle.callbacks")
public class BLCConfigClass {

  @Bean(initMethod = "init", destroyMethod = "destroy")
  public ExampleBean1 exampleBean1() {
    return new ExampleBean1();
  }
}
TestInitDestroy.java
package bean.lifecycle.callbacks;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestInitDestroy {
  public static void main(String[] args) {

    ConfigurableApplicationContext context
        = new AnnotationConfigApplicationContext(BLCConfigClass.class);
    //This is important
    context.registerShutdownHook();
  }
}
ExampleBean1.java
package bean.lifecycle.callbacks;

public class ExampleBean1 {

  public void init() {
    System.out.println("Initialization method of " + this.getClass().getName());
  }

  public void destroy() {
    System.out.println("Destroy method of " + this.getClass().getName());
  }
}

Output:

Initialization method of bean.lifecycle.callbacks.ExampleBean1
Destroy method of bean.lifecycle.callbacks.ExampleBean1

If you are using XML based configuration, the callbacks can be easily configured as below.

XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
       
  <bean id="exampleBean" class="bean.lifecycle.callbacks.ExampleBean1" init-method="init" destroy-method="destroy"/>

</beans>

Implement the InitializingBean and DisposableBean interfaces

Instead of configuring the callbacks in bean definition, you can implement InitializingBean.afterPropertiesSet() method for initialization and DisposableBean.destroy() method for destruction callbacks in your bean. The below example represents this concept.

ExampleBean2.java
package bean.lifecycle.callbacks;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class ExampleBean2 implements InitializingBean, DisposableBean {

  @Override
  public void afterPropertiesSet() throws Exception {
    System.out.println("Initialization method of " + this.getClass().getName());
  }

  @Override
  public void destroy() throws Exception {
    System.out.println("Destroy method of " + this.getClass().getName());
  }
}

It is not recommended to implement InitializingBean and DisposableBean as it unnecessarily creates tight coupling. Instead use the bean configuration or JSR-250 annotations as discussed before.

3. Default Initialization and Destroy methods

There can be a use case where instead of configuring the initialization and destruction callbacks individually, you want to configure them globally. This way you can just configure it once and apply it on all beans by specifying it with default-init-method and default-destroy-method.

Care must be taken in this approach to ensure that all the Java Classes use the same method names as the initialization & destruction callbacks. E.g. you can use init(), initialize() etc for initialization and dispose(), clear() or destroy() etc for destruction callbacks. You can make all your classes implement a custom interface to ensure the method name remains the same.

I have represented a simple example below.

XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context.xsd"
   default-init-method="init" default-destroy-method="destroy">
   
 <context:component-scan base-package="bean.dfault.callbacLks"/>
 
</beans>
LifecycleBean.java
package bean.dfault.callbacks;

public interface LifecycleBean {

  //initialization callback
  public void init();

  //Destruction callbacks
  public void destroy();
}
MyServiceA.java
package bean.dfault.callbacks;
import org.springframework.stereotype.Component;
@Component
public class MyServiceA implements LifecycleBean {

  @Override
  public void init() {
    System.out.println("Initialization method of " + this.getClass().getName());
  }

  @Override
  public void destroy() {
    System.out.println("Destroy method of " + this.getClass().getName());
  }
}
MyServiceB.java
package bean.dfault.callbacks;
import org.springframework.stereotype.Component;

@Component
public class MyServiceB implements LifecycleBean {

  @Override
  public void init() {
    System.out.println("Initialization method of " + this.getClass().getName());
  }

  @Override
  public void destroy() {
    System.out.println("Destroy method of " + this.getClass().getName());
  }
}
TestDefaultCallbacks.java
package bean.dfault.callbacks;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDefaultCallbacks {
  public static void main(String[] args) {
  
    ConfigurableApplicationContext context
        = new ClassPathXmlApplicationContext("bean.config.xml");

    //This is important
    context.registerShutdownHook();
  }
}

Output:

Initialization method of bean.dfault.callbacks.MyServiceA
Initialization method of bean.dfault.callbacks.MyServiceB
Destroy method of bean.dfault.callbacks.MyServiceB
Destroy method of bean.dfault.callbacks.MyServiceA

What happens if all 3 mechanisms combined?

If you combine all the above mentioned 3 mechanisms into a single bean, the callbacks will be executed in the following order:

If the bean is getting created

  1. First, the method annotated with @PostConstruct is executed.
  2. Then, the afterPropertiesSet() defined by InitializingBean gets executed.
  3. Third, a custom configured init() gets executed.

Similarly, if the bean is getting destroyed

  1. Method with @PreDestroy gets executed
  2. Then the method destroy() from DisposableBean is executed.
  3. Finally, a custom configured destroy() method runs.

4. Spring Lifecycle interface Startup and Shutdown callbacks

The Lifecycle interface of Spring framework provides start(), stop() and isRunning()to invoke callbacks when the ApplicationContext receives start and stop signals at runtime. This allows the programmer to control the processes running in the background.

The Lifecycle interface looks as below.

Lifecycle.java
package org.springframework.context;

public interface Lifecycle {
  void start();
  void stop();
  boolean isRunning();
}

Here is an example that shows the working of Lifecycle interface.

LifecycleExampleBean.java
package bean.start.stop.callbacks;
import org.springframework.context.Lifecycle;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class LifecycleExampleBean implements Lifecycle {
  private boolean status = false;

  @Override
  public void start() {
    System.out.println("Inside the Lifecycle start method");
    this.status = true;
  }

  @Override
  public void stop() {
    System.out.println("Inside the Lifecycle stop method");
    this.status = false;
  }

  @Override
  public boolean isRunning() {
    return this.status;
  }
}
TestLifecycleBean.java
package bean.start.stop.callbacks;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestLifecycleBean {
  public static void main(String[] args) {

    ConfigurableApplicationContext context
        = new AnnotationConfigApplicationContext(LifecycleExampleBean.class);

    context.start();
    context.getBean(LifecycleExampleBean.class);
    context.stop();
    context.start();

    //This is important - shutdown gracefully
    context.registerShutdownHook();
  }
}

Output:

Inside the Lifecycle start method
Inside the Lifecycle stop method
Inside the Lifecycle start method
Inside the Lifecycle stop method

NOTE: Spring Lifecycle interface is a plain contract for explicit start and explicit stop notifications. It does not work with the auto-start at context refresh time.

For fine-grained control over auto-startup of a specific bean (including startup phases), consider implementing SmartLifecycle instead.

The SmartLifecycle interface

When we have a use case where object with certain typ should start first then another type, in these cases SmartLifecycle plays an important role. The getPhase() method returns an integer value, objects with the lowest phase value start first. So the object that returns Integer.MIN_VALUE as the phase value will be started first and will be stopped last.

SmartLifecycle.java
public interface SmartLifecycle extends Lifecycle, Phased {
    boolean isAutoStartup();
    void stop(Runnable callback);
}
Phased.java
public interface Phased {
    int getPhase();
}

ApplicationContextAware, BeanNameAware, and Other Aware interfaces

We are touching an advanced part of the Spring Framework. This concept is kind of contradicts the Spring IoC and Dependency Injection concepts.

If you need an instance of `ApplicationContext inside your bean for whatsoever reason, your bean needs to implement ApplicationContextAware interface. This is a bit reverse of the dependency injection, but will not get into that discussion now.

ApplicationContextAware.java
public interface ApplicationContextAware {
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

Similarly, if you need to modify the bean name, you can implement BeanNameAware interface in you bean. This will allow you to rename your bean before it is ready to use.

BeanNameAware.java
public interface BeanNameAware {
    void setBeanName(String name) throws BeansException;
}

Other Aware Interfaces

Apart from ApplicationContextAware and BeanNameAware interfaces, spring offers a range of Aware interfaces. Refer to the Official Spring Doc for more information on Aware interfaces.

Aware Interface Method to OverrideExplanation
ApplicationContextAwarevoid setApplicationContext (ApplicatinContext …) Implement this interface to get access to ApplicationContext
ApplicationEventPublisherAwarevoid setApplicationEventPublisher (ApplicationEventPublisher …)Class implements this interface to publish custom ApplicationEvent.
BeanClassLoaderAwarevoid setBeanClassLoader (ClassLoader …)Custom callback to supply bean class loader.
BeanFactoryAwarevoid setBeanFactory (BeanFactory …)Custom callback to supply BeanFactory to the bean instance
BeanNameAwarevoid setBeanName (String name)Set the name of the bean.
BootstrapContextAwarevoid setBootstrapContext (BootstrapContext …)Set the BootstrapContext for the bean
LoadTimeWeaverAwarevoid setLoadTimeWeaver (LoadTimeWeaver …) Waver for processing class definition at load time.
MessageSourceAwarevoid setMessageSource (MessageSource …)Configure the startegy for resolving messages.
NotificationPublisherAwarevoid setNotificationPublisher (NotificationPublisher …)Spring JMX notification publisher.
ResourceLoaderAwarevoid setResourceLoader (ResourceLoader …)Configure loader for low-level access to resources.
ServletConfigAwarevoid setServletConfig (ServletConfig …)Modify the ServletConfig in which the bean runs.
ServletContextAwarevoid setServletContext (ServletContext …)Set the ServletContext in which bean runs.

I hope you got a good understanding of Spring bean lifecycle callbacks, it is a good practice to just stick to JSR-250 annotations. Bean aware interfaces are handy when you need more control over Spring. Spring also provides @EventListener to customize behavior, discussed in another article. You can also download the code examples from GitHub.


By |Last Updated: April 2nd, 2024|Categories: Spring Framework|

Table of Contents