Spring – [ Bean Life Cycle ]

The life cycle of a Spring bean is a fundamental concept in the Spring Framework.

Understanding the bean life cycle is crucial for effectively managing the behavior and state of beans within a Spring application.

A bean is simply an object managed by the Spring IoC container.

Instead of you instantiating and wiring up objects yourself, Spring does it for you.

But Spring doesn’t just create beans – it manages their entire life cycle.

1. Bean Life Cycle Overview

The Spring container manages the lifecycle of singleton-scoped beans.

Within this scope, Spring knows precisely when the bean is created, initialized, and destroyed.

For prototype-scoped beans, Spring is only responsible for creation.

Once the container creates the bean instance, it hands over management to the client code, and the Spring container no longer tracks its lifecycle.

Understanding the Spring lifecycle allows you to perform actions at specific points in a bean’s lifecycle.

These points can be many, but typically, actions are performed after the bean is initialized and before it is destroyed.

Below is the simplified flow of how a bean lives inside the Spring container :

  • 1. Instantiation → Bean is created (constructor or factory method).
  • 2. Dependency Injection → Properties and dependencies are set.
  • 3. Aware Interfaces → Bean can access container-related information (BeanNameAware, ApplicationContextAware).
  • 4. BeanPostProcessors → Custom logic before/after initialization.
  • 5. Initialization → Special init methods (@PostConstruct, afterPropertiesSet(), etc).
  • 6. Ready-to-use → The bean is fully initialized and available for business logic.
  • 7. Destruction → Cleanup logic before the bean is removed (@PreDestroy, destroy()).

2. Detailed Walk Through of Each Phase

2.1 Plain Java Objects (No Spring Yet)

Before diving into Spring and its bean lifecycle, let us start with a very simple Java example.

We have two classes :

  • Address : represents a city where the user lives.
  • User : represents a user with a name and an associated address.

Both classes have constructors, setters, getters, and toString() methods.

To make things more illustrative, every method prints a message when it is called, so we can clearly see when and how each part of the object is executed.

package com.example19;

public class BeanLifeCycleExample {
    static class Address {
        private String city;

        public Address() {
            System.out.println("\nAddress constructor called");
        }

        public Address(String city) {
            this.city = city;
            System.out.println("\nAddress constructor with city called: " + city);
        }

        public String getCity() {
            System.out.println("\ngetCity called : " + city);
            return city;
        }

        public void setCity(String city) {
            System.out.println("\nsetCity called with: " + city);
            this.city = city;
        }

        @Override
        public String toString() {
            return "Address{city='" + city + "'}";
        }
    }

    static class User {
        private String name;
        private Address address;

        public User() {
            System.out.println("\nUser constructor called");
        }

        public User(String name, Address address) {
            this.name = name;
            this.address = address;
            System.out.println("\nUser constructor with name and address called: " + name + ", " + address);
        }

        public void setName(String name) {
            System.out.println("\nsetName called with: " + name);
            this.name = name;
        }

        public String getName() {
            System.out.println("\ngetName called: " + name);
            return name;
        }

        public Address getAddress() {
            System.out.println("\ngetAddress called: " + address);
            return address;
        }

        public void setAddress(Address address) {
            System.out.println("\nsetAddress called with: " + address);
            this.address = address;
        }

        @Override
        public String toString() {
            return "\nUser [name=" + name + ", address=" + address + "]";
        }
    }

    public static void main(String[] args) {
        Address address = new Address("New York");
        User user = new User("Alice", address);

        System.out.println(user);
    }
}
/*
Output:
    Address constructor with city called: New York
    
    User constructor with name and address called: Alice, Address{city='New York'}
    
    User [name=Alice, address=Address{city='New York'}]
*/

2.2 Letting Spring Manage Our Objects

In the previous example, we created our objects manually with new.

Now, let us see how Spring can take over this responsibility and manage the lifecycle of our objects (called beans in Spring terminology).

  1package com.example19;
  2
  3import org.springframework.beans.factory.config.BeanDefinition;
  4import org.springframework.beans.factory.support.BeanDefinitionBuilder;
  5import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  6
  7public class BeanLifeCycleExample2 {
  8    static class Address {
  9        private String city;
 10
 11        public Address() {
 12            System.out.println("\nAddress constructor called");
 13        }
 14
 15        public Address(String city) {
 16            this.city = city;
 17            System.out.println("\nAddress constructor with city called: " + city);
 18        }
 19
 20        public String getCity() {
 21            System.out.println("\ngetCity called : " + city);
 22            return city;
 23        }
 24
 25        public void setCity(String city) {
 26            System.out.println("\nsetCity called with: " + city);
 27            this.city = city;
 28        }
 29
 30        @Override
 31        public String toString() {
 32            return "Address{city='" + city + "'}";
 33        }
 34    }
 35
 36    static class User {
 37        private String name;
 38        private Address address;
 39
 40        public User() {
 41            System.out.println("\nUser constructor called");
 42        }
 43
 44        public User(String name, Address address) {
 45            this.name = name;
 46            this.address = address;
 47            System.out.println("\nUser constructor with name and address called: " + name + ", " + address);
 48        }
 49
 50        public void setName(String name) {
 51            System.out.println("\nsetName called with: " + name);
 52            this.name = name;
 53        }
 54
 55        public String getName() {
 56            System.out.println("\ngetName called: " + name);
 57            return name;
 58        }
 59
 60        public Address getAddress() {
 61            System.out.println("\ngetAddress called: " + address);
 62            return address;
 63        }
 64
 65        public void setAddress(Address address) {
 66            System.out.println("\nsetAddress called with: " + address);
 67            this.address = address;
 68        }
 69
 70        @Override
 71        public String toString() {
 72            return "\nUser [name=" + name + ", address=" + address + "]";
 73        }
 74    }
 75
 76    public static void main(String[] args) {
 77        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 78
 79        BeanDefinition addressDef = BeanDefinitionBuilder
 80                .genericBeanDefinition(Address.class)
 81                .addConstructorArgValue("New York")
 82                .getBeanDefinition();
 83        ctx.registerBeanDefinition("address", addressDef);
 84
 85        BeanDefinition userDef = BeanDefinitionBuilder
 86                .genericBeanDefinition(User.class)
 87                .addConstructorArgValue("Alice")
 88                .addConstructorArgReference("address")
 89                .getBeanDefinition();
 90        ctx.registerBeanDefinition("user", userDef);
 91
 92        ctx.refresh();
 93
 94        User user = ctx.getBean(User.class);
 95
 96        System.out.println(user);
 97
 98        ctx.close();
 99    }
100}
101/*
102 * Output:
103 * Address constructor with city called: New York
104 * User constructor with name and address called: Alice, Address{city='New York'}
105 * User [name=Alice, address=Address{city='New York'}]
106 */

Key parts of the code :

Create an ApplicationContext (line 77)
  • This is the Spring IoC container.
  • It is responsible for creating, wiring, and managing our beans.
Define bean metadata (line 79 – 90)
  • Here we tell Spring to create an instance of the Address class and pass “New York” into the constructor when constructing it.
  • Similarly, for the User.
  • Notice how instead of passing the Address object directly, we give Spring a reference to another bean (address).
  • Spring will resolve this dependency and inject it automatically.
Start the container (line 92)
  • At this point, Spring looks at all registered bean definitions, creates the objects, injects dependencies, and prepares everything for use.
Retrieve the bean (line 94)
  • Here we simply ask Spring for the User bean, and we get back a fully initialized object with its Address already set.
Close the context (line 98)
  • Closing the context allows Spring to trigger the destroy phase of the bean lifecycle (important when resources need to be cleaned up).

2.3 InstantiationAwareBeanPostProcessor

InstantiationAwareBeanPostProcessor is an interface in the Spring Framework that extends the BeanPostProcessor interface.

It provides additional hooks into the Spring bean lifecycle, allowing developers to intervene during the instantiation and property population phases of a bean’s lifecycle.

This interface is particularly useful when you need fine-grained control over bean creation, such as modifying bean properties before they are set or suppressing the instantiation of certain beans.

  1package com.example19;
  2
  3import org.springframework.beans.BeansException;
  4import org.springframework.beans.PropertyValues;
  5import org.springframework.beans.factory.config.BeanDefinition;
  6import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
  7import org.springframework.beans.factory.support.BeanDefinitionBuilder;
  8import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  9
 10public class BeanLifeCycleExample3 {
 11    static class Address {
 12        private String city;
 13
 14        public Address() {
 15            System.out.println("\nAddress constructor called");
 16        }
 17
 18        public String getCity() {
 19            System.out.println("\ngetCity called : " + city);
 20            return city;
 21        }
 22
 23        public void setCity(String city) {
 24            System.out.println("\nsetCity called with: " + city);
 25            this.city = city;
 26        }
 27
 28        @Override
 29        public String toString() {
 30            return "Address{city='" + city + "'}";
 31        }
 32    }
 33
 34    static class User {
 35        private String name;
 36        private Address address;
 37
 38        public User() {
 39            System.out.println("\nUser constructor called");
 40        }
 41
 42        public void setName(String name) {
 43            System.out.println("\nsetName called with: " + name);
 44            this.name = name;
 45        }
 46
 47        public String getName() {
 48            System.out.println("\ngetName called: " + name);
 49            return name;
 50        }
 51
 52        public Address getAddress() {
 53            System.out.println("\ngetAddress called: " + address);
 54            return address;
 55        }
 56
 57        public void setAddress(Address address) {
 58            System.out.println("\nsetAddress called with: " + address);
 59            this.address = address;
 60        }
 61
 62        @Override
 63        public String toString() {
 64            return "\nUser [name=" + name + ", address=" + address + "]";
 65        }
 66    }
 67
 68    public static void main(String[] args) {
 69        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 70
 71        // Register Address bean definition with property injection
 72        BeanDefinition addressDef = BeanDefinitionBuilder
 73                .genericBeanDefinition(Address.class)
 74                .addPropertyValue("city", "New York")
 75                .getBeanDefinition();
 76        ctx.registerBeanDefinition("address", addressDef);
 77
 78        // Register User bean definition with property injection
 79        BeanDefinition userDef = BeanDefinitionBuilder
 80                .genericBeanDefinition(User.class)
 81                .addPropertyValue("name", "Alice")
 82                .addPropertyReference("address", "address")
 83                .getBeanDefinition();
 84        ctx.registerBeanDefinition("user", userDef);
 85
 86        ctx.addBeanFactoryPostProcessor(
 87                beanFactory -> beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
 88                    @Override
 89                    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
 90                            throws BeansException {
 91                        if (beanClass == User.class || beanClass == Address.class) {
 92                            System.out.println(
 93                                    "\n1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: "
 94                                            + beanName);
 95                        }
 96                        return null;
 97                    }
 98
 99                    @Override
100                    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
101                        if (bean instanceof Address || bean instanceof User) {
102                            System.out.println(
103                                    "\n2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: "
104                                            + beanName);
105                        }
106                        return true;
107                    }
108
109                    @Override
110                    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
111                            throws BeansException {
112                        if (bean instanceof Address || bean instanceof User) {
113                            System.out.println(
114                                    "\n3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: "
115                                            + beanName);
116
117                            System.out.println("Current PropertyValues: " + pvs);
118                        }
119                        return pvs;
120                    }
121                }));
122
123        ctx.refresh();
124
125        User user = ctx.getBean(User.class);
126
127        System.out.println(user);
128
129        ctx.close();
130    }
131}
132/*
133 * Output:
134 * 
135 * 1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: address
136 * Address constructor called
137 * 2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: address
138 * 3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: address
139 * Current PropertyValues: PropertyValues: length=1; bean property 'city'
140 * setCity called with: New York
141 * 1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: user
142 * User constructor called
143 * 2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: user
144 * 3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: user
145 * Current PropertyValues: PropertyValues: length=2; bean property 'name'; bean property 'address'
146 * setName called with: Alice
147 * setAddress called with: Address{city='New York'}
148 * User [name=Alice, address=Address{city='New York'}]
149 */

Key additions in this example :

Property injection instead of constructor injection (line 74 81 82)
  • Instead of passing constructor arguments, we now configure beans using property injection.
  • This is useful because it lets us observe how Spring injects values after instantiation but before the bean is fully ready.
Adding a BeanPostProcessor (line 84 – 119)
  • Here, we register an InstantiationAwareBeanPostProcessor.
  • Think of it as a hook into the bean lifecycle, allowing us to run custom logic at specific moments.
We override below methods in InstantiationAwareBeanPostProcessor
  • postProcessBeforeInstantiation : It is called before Spring creates the bean instance and we log a message whenever Spring is about to instantiate User or Address.
  • postProcessAfterInstantiation : It is called immediately after the bean is created, but before its properties are set and it gives us a chance to inspect the “freshly constructed” object.
  • postProcessProperties : It is called right before property values are applied to the bean so that we can see which properties Spring is about to inject and even modify them if we want.

2.4 Making Beans “Aware” of Their Context – The Aware Interfaces

Spring provides several callback-style interfaces that allow a bean to receive specific framework objects during initialization.

When a bean implements one of these interfaces, Spring automatically calls its corresponding setter method at the appropriate moment in the lifecycle.

Some of the most common ones are :

INTERFACECALLBACK METHODPURPOSE
BeanNameAwaresetBeanName(String name)Gives the bean its own name as known by the Spring container.
BeanFactoryAwaresetBeanFactory(BeanFactory factory)Gives access to the BeanFactory that created it.
ApplicationContextAwaresetApplicationContext(ApplicationContext ctx)Provides access to the entire application context, allowing the bean to look up other beans or resources.

By implementing these interfaces, beans can interact with the container that manages them – a key part of the Inversion of Control concept.

  1package com.example19;
  2
  3import org.springframework.beans.BeansException;
  4import org.springframework.beans.PropertyValues;
  5import org.springframework.beans.factory.BeanFactory;
  6import org.springframework.beans.factory.BeanFactoryAware;
  7import org.springframework.beans.factory.BeanNameAware;
  8import org.springframework.beans.factory.config.BeanDefinition;
  9import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
 10import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 11import org.springframework.context.ApplicationContext;
 12import org.springframework.context.ApplicationContextAware;
 13import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 14
 15public class BeanLifeCycleExample4 {
 16    static class Address implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
 17        private String city;
 18
 19        public Address() {
 20            System.out.println("\nAddress constructor called");
 21        }
 22
 23        public String getCity() {
 24            System.out.println("\ngetCity called : " + city);
 25            return city;
 26        }
 27
 28        public void setCity(String city) {
 29            System.out.println("\nsetCity called with: " + city);
 30            this.city = city;
 31        }
 32
 33        @Override
 34        public void setBeanName(String name) {
 35            System.out.println("\nBeanNameAware#setBeanName called: " + name);
 36        }
 37
 38        @Override
 39        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
 40            System.out.println("\nBeanFactoryAware#setBeanFactory called: " + beanFactory.getClass());
 41        }
 42
 43        @Override
 44        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
 45            System.out.println(
 46                    "\nApplicationContextAware#setApplicationContext called: " + applicationContext.getClass());
 47        }
 48
 49        @Override
 50        public String toString() {
 51            return "Address{city='" + city + "'}";
 52        }
 53    }
 54
 55    static class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
 56        private String name;
 57        private Address address;
 58
 59        public User() {
 60            System.out.println("\nUser constructor called");
 61        }
 62
 63        public void setName(String name) {
 64            System.out.println("\nsetName called with: " + name);
 65            this.name = name;
 66        }
 67
 68        public String getName() {
 69            System.out.println("\ngetName called: " + name);
 70            return name;
 71        }
 72
 73        public Address getAddress() {
 74            System.out.println("\ngetAddress called: " + address);
 75            return address;
 76        }
 77
 78        public void setAddress(Address address) {
 79            System.out.println("\nsetAddress called with: " + address);
 80            this.address = address;
 81        }
 82
 83        @Override
 84        public void setBeanName(String name) {
 85            System.out.println("\nBeanNameAware#setBeanName called: " + name);
 86        }
 87
 88        @Override
 89        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
 90            System.out.println("\nBeanFactoryAware#setBeanFactory called: " + beanFactory.getClass());
 91        }
 92
 93        @Override
 94        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
 95            System.out.println(
 96                    "\nApplicationContextAware#setApplicationContext called: " + applicationContext.getClass());
 97        }
 98
 99        @Override
100        public String toString() {
101            return "\nUser [name=" + name + ", address=" + address + "]";
102        }
103    }
104
105    public static void main(String[] args) {
106        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
107
108        // Register Address bean definition with property injection
109        BeanDefinition addressDef = BeanDefinitionBuilder
110                .genericBeanDefinition(Address.class)
111                .addPropertyValue("city", "New York")
112                .getBeanDefinition();
113        ctx.registerBeanDefinition("address", addressDef);
114
115        // Register User bean definition with property injection
116        BeanDefinition userDef = BeanDefinitionBuilder
117                .genericBeanDefinition(User.class)
118                .addPropertyValue("name", "Alice")
119                .addPropertyReference("address", "address")
120                .getBeanDefinition();
121        ctx.registerBeanDefinition("user", userDef);
122
123        ctx.addBeanFactoryPostProcessor(
124                beanFactory -> beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
125                    @Override
126                    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
127                            throws BeansException {
128                        if (beanClass == User.class || beanClass == Address.class) {
129                            System.out.println(
130                                    "\n1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: "
131                                            + beanName);
132                        }
133                        return null;
134                    }
135
136                    @Override
137                    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
138                        if (bean instanceof Address || bean instanceof User) {
139                            System.out.println(
140                                    "\n2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: "
141                                            + beanName);
142                        }
143                        return true;
144                    }
145
146                    @Override
147                    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
148                            throws BeansException {
149                        if (bean instanceof Address || bean instanceof User) {
150                            System.out.println(
151                                    "\n3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: "
152                                            + beanName);
153
154                            System.out.println("Current PropertyValues: " + pvs);
155                        }
156                        return pvs;
157                    }
158                }));
159
160        ctx.refresh();
161
162        User user = ctx.getBean(User.class);
163
164        System.out.println(user);
165
166        ctx.close();
167    }
168}
169/*
170Output:
171    1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: address
172    
173    Address constructor called
174    
175    2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: address
176    
177    3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: address
178    Current PropertyValues: PropertyValues: length=1; bean property 'city'
179    
180    setCity called with: New York
181    
182    BeanNameAware#setBeanName called: address
183    
184    BeanFactoryAware#setBeanFactory called: class org.springframework.beans.factory.support.DefaultListableBeanFactory
185    
186    ApplicationContextAware#setApplicationContext called: class org.springframework.context.annotation.AnnotationConfigApplicationContext
187    
188    1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: user
189    
190    User constructor called
191    
192    2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: user
193    
194    3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: user
195    Current PropertyValues: PropertyValues: length=2; bean property 'name'; bean property 'address'
196    
197    setName called with: Alice
198    
199    setAddress called with: Address{city='New York'}
200    
201    BeanNameAware#setBeanName called: user
202    
203    BeanFactoryAware#setBeanFactory called: class org.springframework.beans.factory.support.DefaultListableBeanFactory
204    
205    ApplicationContextAware#setApplicationContext called: class org.springframework.context.annotation.AnnotationConfigApplicationContext
206    
207    User [name=Alice, address=Address{city='New York'}]
208*/

In above code snippet, both Address and User implement :

  • BeanNameAware
  • BeanFactoryAware
  • ApplicationContextAware

Each implementation logs when its callback method is invoked, and these messages let us trace exactly when in the lifecycle these callbacks occur.

2.5 BeanPostProcessor

This processor allows us to intercept beans both before and after their initialization phase, giving Spring a flexible mechanism to modify, wrap, or enhance beans.

It callbacks occur after all dependencies are injected and after the Aware interfaces have been called – that’s the point where the bean is almost fully ready.

package com.example19;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class BeanLifeCycleExample5 {
    static class Address implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
        private String city;

        public Address() {
            System.out.println("\nAddress constructor called");
        }

        public String getCity() {
            System.out.println("\ngetCity called : " + city);
            return city;
        }

        public void setCity(String city) {
            System.out.println("\nsetCity called with: " + city);
            this.city = city;
        }

        @Override
        public void setBeanName(String name) {
            System.out.println("\nBeanNameAware#setBeanName called: " + name);
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("\nBeanFactoryAware#setBeanFactory called: " + beanFactory.getClass());
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println(
                    "\nApplicationContextAware#setApplicationContext called: " + applicationContext.getClass());
        }

        @Override
        public String toString() {
            return "Address{city='" + city + "'}";
        }
    }

    static class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
        private String name;
        private Address address;

        public User() {
            System.out.println("\nUser constructor called");
        }

        public void setName(String name) {
            System.out.println("\nsetName called with: " + name);
            this.name = name;
        }

        public String getName() {
            System.out.println("\ngetName called: " + name);
            return name;
        }

        public Address getAddress() {
            System.out.println("\ngetAddress called: " + address);
            return address;
        }

        public void setAddress(Address address) {
            System.out.println("\nsetAddress called with: " + address);
            this.address = address;
        }

        @Override
        public void setBeanName(String name) {
            System.out.println("\nBeanNameAware#setBeanName called: " + name);
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("\nBeanFactoryAware#setBeanFactory called: " + beanFactory.getClass());
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println(
                    "\nApplicationContextAware#setApplicationContext called: " + applicationContext.getClass());
        }

        @Override
        public String toString() {
            return "\nUser [name=" + name + ", address=" + address + "]";
        }
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

        // Register Address bean definition with property injection
        BeanDefinition addressDef = BeanDefinitionBuilder
                .genericBeanDefinition(Address.class)
                .addPropertyValue("city", "New York")
                .getBeanDefinition();
        ctx.registerBeanDefinition("address", addressDef);

        // Register User bean definition with property injection
        BeanDefinition userDef = BeanDefinitionBuilder
                .genericBeanDefinition(User.class)
                .addPropertyValue("name", "Alice")
                .addPropertyReference("address", "address")
                .getBeanDefinition();
        ctx.registerBeanDefinition("user", userDef);

        ctx.addBeanFactoryPostProcessor(
                beanFactory -> beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
                    @Override
                    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
                            throws BeansException {
                        if (beanClass == User.class || beanClass == Address.class) {
                            System.out.println(
                                    "\n1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: "
                                            + beanName);
                        }
                        return null;
                    }

                    @Override
                    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
                        if (bean instanceof Address || bean instanceof User) {
                            System.out.println(
                                    "\n2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: "
                                            + beanName);
                        }
                        return true;
                    }

                    @Override
                    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
                            throws BeansException {
                        if (bean instanceof Address || bean instanceof User) {
                            System.out.println(
                                    "\n3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: "
                                            + beanName);

                            System.out.println("Current PropertyValues: " + pvs);
                        }
                        return pvs;
                    }
                }));

        // Add BeanPostProcessor to show before/after initialization hooks
        ctx.addBeanFactoryPostProcessor(beanFactory -> beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof Address || bean instanceof User) {
                    System.out.println("\n4.BeanPostProcessor#postProcessBeforeInitialization called for: " + beanName);
                }
                return bean;
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof Address || bean instanceof User) {
                    System.out.println("\n5.BeanPostProcessor#postProcessAfterInitialization called for: " + beanName);
                }
                return bean;
            }
        }));

        ctx.refresh();

        User user = ctx.getBean(User.class);

        System.out.println(user);

        ctx.close();
    }
}
/*
    Output:
        1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: address
        
        Address constructor called
        
        2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: address
        
        3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: address
        Current PropertyValues: PropertyValues: length=1; bean property 'city'
        
        setCity called with: New York
        
        BeanNameAware#setBeanName called: address
        
        BeanFactoryAware#setBeanFactory called: class org.springframework.beans.factory.support.DefaultListableBeanFactory
        
        ApplicationContextAware#setApplicationContext called: class org.springframework.context.annotation.AnnotationConfigApplicationContext
        
        4.BeanPostProcessor#postProcessBeforeInitialization called for: address
        
        5.BeanPostProcessor#postProcessAfterInitialization called for: address
        
        1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: user
        
        User constructor called
        
        2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: user
        
        3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: user
        Current PropertyValues: PropertyValues: length=2; bean property 'name'; bean property 'address'
        
        setName called with: Alice
        
        setAddress called with: Address{city='New York'}
        
        BeanNameAware#setBeanName called: user
        
        BeanFactoryAware#setBeanFactory called: class org.springframework.beans.factory.support.DefaultListableBeanFactory
        
        ApplicationContextAware#setApplicationContext called: class org.springframework.context.annotation.AnnotationConfigApplicationContext
        
        4.BeanPostProcessor#postProcessBeforeInitialization called for: user
        
        5.BeanPostProcessor#postProcessAfterInitialization called for: user
        
        User [name=Alice, address=Address{city='New York'}]
    */

Initialization happens between the two post-processor calls.

If we were to add @PostConstruct or implement InitializingBean.afterPropertiesSet(), they would execute between these two post-processor phases.

postProcessBeforeInitialization()

  • @PostConstruct
  • afterPropertiesSet()
  • custom init-method

postProcessAfterInitialization()

This is why the naming makes sense – the “before initialization” callback is invoked right before any init logic, and the “after initialization” callback is invoked right after it.

2.6 Implementing Initialization and Destruction Callbacks

InitializingBean and DisposableBean are two interfaces provided by the Spring Framework that allow beans to define custom initialization and destruction logic.

  • afterPropertiesSet() : called after dependency injection and before the bean is ready for use.
  • destroy() : called just before the bean is removed from the context.

In below code snippet, both User and Address implement InitializingBean and DisposableBean interfaces to define their own initialization and destruction logic.

package com.example19;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class BeanLifeCycleExample6 {
    static class Address
            implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
        private String city;

        public Address() {
            System.out.println("\nAddress constructor called");
        }

        public String getCity() {
            System.out.println("\ngetCity called : " + city);
            return city;
        }

        public void setCity(String city) {
            System.out.println("\nsetCity called with: " + city);
            this.city = city;
        }

        @Override
        public void setBeanName(String name) {
            System.out.println("\nBeanNameAware#setBeanName called: " + name);
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("\nBeanFactoryAware#setBeanFactory called: " + beanFactory.getClass());
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println(
                    "\nApplicationContextAware#setApplicationContext called: " + applicationContext.getClass());
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("\nInitializingBean#afterPropertiesSet called for Address");
        }

        @Override
        public void destroy() throws Exception {
            System.out.println("\nDisposableBean#destroy called for Address");
        }

        @Override
        public String toString() {
            return "Address{city='" + city + "'}";
        }
    }

    static class User
            implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
        private String name;
        private Address address;

        public User() {
            System.out.println("\nUser constructor called");
        }

        public void setName(String name) {
            System.out.println("\nsetName called with: " + name);
            this.name = name;
        }

        public String getName() {
            System.out.println("\ngetName called: " + name);
            return name;
        }

        public Address getAddress() {
            System.out.println("\ngetAddress called: " + address);
            return address;
        }

        public void setAddress(Address address) {
            System.out.println("\nsetAddress called with: " + address);
            this.address = address;
        }

        @Override
        public void setBeanName(String name) {
            System.out.println("\nBeanNameAware#setBeanName called: " + name);
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("\nBeanFactoryAware#setBeanFactory called: " + beanFactory.getClass());
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println(
                    "\nApplicationContextAware#setApplicationContext called: " + applicationContext.getClass());
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("\nInitializingBean#afterPropertiesSet called for User");
        }

        @Override
        public void destroy() throws Exception {
            System.out.println("\nDisposableBean#destroy called for User");
        }

        @Override
        public String toString() {
            return "\nUser [name=" + name + ", address=" + address + "]";
        }
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

        // Register Address bean definition with property injection
        BeanDefinition addressDef = BeanDefinitionBuilder
                .genericBeanDefinition(Address.class)
                .addPropertyValue("city", "New York")
                .getBeanDefinition();
        ctx.registerBeanDefinition("address", addressDef);

        // Register User bean definition with property injection
        BeanDefinition userDef = BeanDefinitionBuilder
                .genericBeanDefinition(User.class)
                .addPropertyValue("name", "Alice")
                .addPropertyReference("address", "address")
                .getBeanDefinition();
        ctx.registerBeanDefinition("user", userDef);

        ctx.addBeanFactoryPostProcessor(
                beanFactory -> beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
                    @Override
                    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
                            throws BeansException {
                        if (beanClass == User.class || beanClass == Address.class) {
                            System.out.println(
                                    "\n1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: "
                                            + beanName);
                        }
                        return null;
                    }

                    @Override
                    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
                        if (bean instanceof Address || bean instanceof User) {
                            System.out.println(
                                    "\n2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: "
                                            + beanName);
                        }
                        return true;
                    }

                    @Override
                    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
                            throws BeansException {
                        if (bean instanceof Address || bean instanceof User) {
                            System.out.println(
                                    "\n3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: "
                                            + beanName);

                            System.out.println("Current PropertyValues: " + pvs);
                        }
                        return pvs;
                    }
                }));

        ctx.addBeanFactoryPostProcessor(beanFactory -> beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof Address || bean instanceof User) {
                    System.out.println("\n4.BeanPostProcessor#postProcessBeforeInitialization called for: " + beanName);
                }
                return bean;
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof Address || bean instanceof User) {
                    System.out.println("\n5.BeanPostProcessor#postProcessAfterInitialization called for: " + beanName);
                }
                return bean;
            }
        }));

        ctx.refresh();

        User user = ctx.getBean(User.class);

        System.out.println(user);

        ctx.close(); // This will trigger DisposableBean#destroy for beans
    }
}
/*
Output :

        1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: address
        
        Address constructor called
        
        2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: address
        
        3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: address
        Current PropertyValues: PropertyValues: length=1; bean property 'city'
        
        setCity called with: New York
        
        BeanNameAware#setBeanName called: address
        
        BeanFactoryAware#setBeanFactory called: class org.springframework.beans.factory.support.DefaultListableBeanFactory
        
        ApplicationContextAware#setApplicationContext called: class org.springframework.context.annotation.AnnotationConfigApplicationContext
        
        4.BeanPostProcessor#postProcessBeforeInitialization called for: address
        
        InitializingBean#afterPropertiesSet called for Address
        
        5.BeanPostProcessor#postProcessAfterInitialization called for: address
        
        1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: user
        
        User constructor called
        
        2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: user
        
        3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: user
        Current PropertyValues: PropertyValues: length=2; bean property 'name'; bean property 'address'
        
        setName called with: Alice
        
        setAddress called with: Address{city='New York'}
        
        BeanNameAware#setBeanName called: user
        
        BeanFactoryAware#setBeanFactory called: class org.springframework.beans.factory.support.DefaultListableBeanFactory
        
        ApplicationContextAware#setApplicationContext called: class org.springframework.context.annotation.AnnotationConfigApplicationContext
        
        4.BeanPostProcessor#postProcessBeforeInitialization called for: user
        
        InitializingBean#afterPropertiesSet called for User
        
        5.BeanPostProcessor#postProcessAfterInitialization called for: user
        
        User [name=Alice, address=Address{city='New York'}]
        
        DisposableBean#destroy called for User
        
        DisposableBean#destroy called for Address
*/

The method afterPropertiesSet() runs between the beforeInitialization and afterInitialization phases.

This confirms it belongs to the initialization step, just before the bean becomes “ready.”

At the end of the program, we call context.close() to trigger the destruction phase.

When the application context shuts down, Spring triggers the destruction callbacks.

This ensures any resources held by the bean – database connections, file handles, thread pools … are released cleanly.

2.7 Modern Lifecycle Callbacks – @PostConstruct and @PreDestroy

Spring offers a modern, annotation-based approach to initialization and cleanup using Jakarta EE annotations :

  • @PostConstruct : runs after dependency injection, before the bean is ready for use.
  • @PreDestroy : runs just before the bean is destroyed.

These annotations are part of Jakarta Annotations (jakarta.annotation), not Spring itself – meaning they are portable across frameworks.

package com.example19;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

public class BeanLifeCycleExample7 {
    static class Address implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
        private String city;

        public Address() {
            System.out.println("\nAddress constructor called");
        }

        public String getCity() {
            System.out.println("\ngetCity called : " + city);
            return city;
        }

        public void setCity(String city) {
            System.out.println("\nsetCity called with: " + city);
            this.city = city;
        }

        @Override
        public void setBeanName(String name) {
            System.out.println("\nBeanNameAware#setBeanName called: " + name);
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("\nBeanFactoryAware#setBeanFactory called: " + beanFactory.getClass());
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println(
                    "\nApplicationContextAware#setApplicationContext called: " + applicationContext.getClass());
        }

        @PostConstruct
        public void postConstructInit() {
            System.out.println("\n@PostConstruct called for Address");
        }

        @PreDestroy
        public void preDestroyCleanup() {
            System.out.println("\n@PreDestroy called for Address");
        }

        @Override
        public String toString() {
            return "Address{city='" + city + "'}";
        }
    }

    static class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
        private String name;
        private Address address;

        public User() {
            System.out.println("\nUser constructor called");
        }

        public void setName(String name) {
            System.out.println("\nsetName called with: " + name);
            this.name = name;
        }

        public String getName() {
            System.out.println("\ngetName called: " + name);
            return name;
        }

        public Address getAddress() {
            System.out.println("\ngetAddress called: " + address);
            return address;
        }

        public void setAddress(Address address) {
            System.out.println("\nsetAddress called with: " + address);
            this.address = address;
        }

        @Override
        public void setBeanName(String name) {
            System.out.println("\nBeanNameAware#setBeanName called: " + name);
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("\nBeanFactoryAware#setBeanFactory called: " + beanFactory.getClass());
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            System.out.println(
                    "\nApplicationContextAware#setApplicationContext called: " + applicationContext.getClass());
        }

        @PostConstruct
        public void postConstructInit() {
            System.out.println("\n@PostConstruct called for User");
        }

        @PreDestroy
        public void preDestroyCleanup() {
            System.out.println("\n@PreDestroy called for User");
        }

        @Override
        public String toString() {
            return "\nUser [name=" + name + ", address=" + address + "]";
        }
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

        // Register Address bean definition with property injection
        BeanDefinition addressDef = BeanDefinitionBuilder
                .genericBeanDefinition(Address.class)
                .addPropertyValue("city", "New York")
                .getBeanDefinition();
        ctx.registerBeanDefinition("address", addressDef);

        // Register User bean definition with property injection
        BeanDefinition userDef = BeanDefinitionBuilder
                .genericBeanDefinition(User.class)
                .addPropertyValue("name", "Alice")
                .addPropertyReference("address", "address")
                .getBeanDefinition();
        ctx.registerBeanDefinition("user", userDef);

        ctx.addBeanFactoryPostProcessor(
                beanFactory -> beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
                    @Override
                    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
                            throws BeansException {
                        if (beanClass == User.class || beanClass == Address.class) {
                            System.out.println(
                                    "\n1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: "
                                            + beanName);
                        }
                        return null;
                    }

                    @Override
                    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
                        if (bean instanceof Address || bean instanceof User) {
                            System.out.println(
                                    "\n2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: "
                                            + beanName);
                        }
                        return true;
                    }

                    @Override
                    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
                            throws BeansException {
                        if (bean instanceof Address || bean instanceof User) {
                            System.out.println(
                                    "\n3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: "
                                            + beanName);

                            System.out.println("Current PropertyValues: " + pvs);
                        }
                        return pvs;
                    }
                }));

        ctx.addBeanFactoryPostProcessor(beanFactory -> beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof Address || bean instanceof User) {
                    System.out.println("\n4.BeanPostProcessor#postProcessBeforeInitialization called for: " + beanName);
                }
                return bean;
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof Address || bean instanceof User) {
                    System.out.println("\n5.BeanPostProcessor#postProcessAfterInitialization called for: " + beanName);
                }
                return bean;
            }
        }));

        ctx.refresh();

        User user = ctx.getBean(User.class);

        System.out.println(user);

        ctx.close(); // This will trigger @PreDestroy for beans
    }
}
/*
Output :

        1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: address
        
        Address constructor called
        
        2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: address
        
        3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: address
        Current PropertyValues: PropertyValues: length=1; bean property 'city'
        
        setCity called with: New York
        
        BeanNameAware#setBeanName called: address
        
        BeanFactoryAware#setBeanFactory called: class org.springframework.beans.factory.support.DefaultListableBeanFactory
        
        ApplicationContextAware#setApplicationContext called: class org.springframework.context.annotation.AnnotationConfigApplicationContext
        
        4.BeanPostProcessor#postProcessBeforeInitialization called for: address
        
        @PostConstruct called for Address
        
        5.BeanPostProcessor#postProcessAfterInitialization called for: address
        
        1.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation called for: user
        
        User constructor called
        
        2.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation called for: user
        
        3.InstantiationAwareBeanPostProcessor#postProcessProperties called for: user
        Current PropertyValues: PropertyValues: length=2; bean property 'name'; bean property 'address'
        
        setName called with: Alice
        
        setAddress called with: Address{city='New York'}
        
        BeanNameAware#setBeanName called: user
        
        BeanFactoryAware#setBeanFactory called: class org.springframework.beans.factory.support.DefaultListableBeanFactory
        
        ApplicationContextAware#setApplicationContext called: class org.springframework.context.annotation.AnnotationConfigApplicationContext
        
        4.BeanPostProcessor#postProcessBeforeInitialization called for: user
        
        @PostConstruct called for User
        
        5.BeanPostProcessor#postProcessAfterInitialization called for: user
        
        User [name=Alice, address=Address{city='New York'}]
        
        @PreDestroy called for User
        
        @PreDestroy called for Address
*/

In above code snippet, no need to implement interfaces like InitializingBean or DisposableBean.

By using @PostConstruct and @PreDestroy annotations, Spring automatically detects these annotations and invokes the corresponding methods at the correct lifecycle phases.