Hook: Interviewers ask this because Spring beans do not just “appear” — the container carefully builds, wires, initializes, and later destroys them.
Question: Explain Bean Lifecycle.
Answer: In Spring Boot, a bean’s lifecycle is the sequence of steps Spring follows from creating the object to destroying it. First Spring instantiates the bean, then fills in its dependencies, then runs initialization callbacks, and finally makes it ready for use. When the application shuts down, Spring calls destruction callbacks for beans it manages, especially singleton beans.
Interview-Ready Answer: In Spring Boot, the bean lifecycle is the full journey a bean takes inside the container. Spring creates the object, injects its dependencies, calls awareness and initialization callbacks like @PostConstruct or afterPropertiesSet(), and then hands the ready bean to the app. On shutdown, Spring calls destroy callbacks such as @PreDestroy for singleton beans, while prototype beans are created on demand and are not automatically destroyed by the container.
BeanFactoryPostProcessor implementations before objects are created. This lets the container modify bean definitions themselves, which is why this phase is earlier than normal bean initialization.BeanPostProcessor is a hook that can intercept bean creation before and after initialization. Many Spring features use this idea, including annotation support and AOP, which is a proxy-based programming technique where Spring wraps a bean to add behavior like transactions.BeanNameAware or ApplicationContextAware, Spring passes useful container information to it. This is the bean learning about its own environment.postProcessBeforeInitialization() on each BeanPostProcessor. Annotation-driven callbacks like @PostConstruct are usually triggered here by a processor.@PostConstruct, InitializingBean.afterPropertiesSet(), and a custom initMethod. Use these for lightweight setup, not slow remote calls.postProcessAfterInitialization(). This is the last chance to wrap the bean, often with a proxy for transactions, security, or logging.@PreDestroy, DisposableBean.destroy(), and a custom destroyMethod. This is the right place to close files, sockets, and connection pools.Bean lifecycle answers are really about when Spring gives you a usable object and who owns cleanup. The key mental model is: Spring builds the object, injects it, prepares it, and later disposes of it if it owns the scope. If you remember that, you can reason about most startup bugs and shutdown bugs quickly.
| Hook | Runs when | Best use | Gotcha |
|---|---|---|---|
@PostConstruct | After injection | Simple setup | Keep it fast |
afterPropertiesSet() | During init | Framework-style checks | Ties code to Spring |
initMethod | After bean creation | Custom startup logic | Configured on the bean |
@PreDestroy | On shutdown | Cleanup | Prototype beans skip it |
| Scope | Created | Destroyed by Spring | Typical use |
|---|---|---|---|
| Singleton | Once per context | Yes | Services |
| Prototype | Each request | No | Stateful helpers |
@PostConstruct, startup can jump from milliseconds to seconds.Memory rule: “Construct, inject, initialize, proxy, serve, destroy.” That six-word chain is the lifecycle in interview form.
Imagine a Spring Boot checkout service in an e-commerce system. It has a payment client, a fraud-check client, and a database repository. During startup, Spring creates those singleton beans, injects their dependencies, and runs initialization so the service is ready before it accepts traffic.
What goes wrong if you misunderstand lifecycle: a developer puts a slow remote metadata call into @PostConstruct. Startup time jumps from 8 seconds to 90 seconds, Kubernetes marks the pod unhealthy, and rollout fails. In logs you may see repeated readiness probe failures, delayed bean initialization messages, or a timeout while the app is still “starting.” Users feel it as a failed deploy or a brief outage, even though the code “works” locally.
package com.example.beanlifecycle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.UUID;
@SpringBootApplication
public class BeanLifecycleApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(BeanLifecycleApplication.class, args);
System.out.println("\n--- Using singleton bean ---");
LifecycleDemoBean bean = context.getBean(LifecycleDemoBean.class);
bean.businessMethod();
System.out.println("\n--- Using prototype bean twice ---");
PrototypeBean p1 = context.getBean(PrototypeBean.class);
PrototypeBean p2 = context.getBean(PrototypeBean.class);
System.out.println("Prototype A id = " + p1.getId());
System.out.println("Prototype B id = " + p2.getId());
System.out.println("Same instance? " + (p1 == p2));
System.out.println("Note: Spring created both, but it will NOT automatically call their destroy callbacks on shutdown.");
System.out.println("\n--- Closing application context ---");
context.close();
}
}
@Configuration
class LifecycleConfig {
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public LifecycleDemoBean lifecycleDemoBean() {
return new LifecycleDemoBean();
}
@Bean
public ExternalResource externalResource() {
return new ExternalResource();
}
@Bean
@Scope("prototype")
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
}
@Component
class LoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LifecycleDemoBean || bean instanceof ExternalResource || bean instanceof PrototypeBean) {
System.out.println("BeanPostProcessor BEFORE init -> " + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof LifecycleDemoBean || bean instanceof ExternalResource || bean instanceof PrototypeBean) {
System.out.println("BeanPostProcessor AFTER init -> " + beanName);
}
return bean;
}
}
class LifecycleDemoBean implements BeanNameAware, InitializingBean, DisposableBean {
@Autowired
private ExternalResource externalResource;
private String beanName;
public LifecycleDemoBean() {
System.out.println("Constructor: object is created, but dependencies are not injected yet.");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("BeanNameAware: bean name is '" + name + "'.");
}
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct: dependencies are injected; safe for lightweight setup.");
}
@Override
public void afterPropertiesSet() {
System.out.println("InitializingBean.afterPropertiesSet(): Spring-specific init callback.");
}
public void customInit() {
System.out.println("customInit(): custom init method configured in @Bean.");
}
public void businessMethod() {
System.out.println("Business method: beanName=" + beanName + ", externalResourceId=" + externalResource.getId());
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy: container is shutting down; clean up here.");
}
@Override
public void destroy() {
System.out.println("DisposableBean.destroy(): final cleanup managed by Spring.");
}
public void customDestroy() {
System.out.println("customDestroy(): custom destroy method configured in @Bean.");
}
}
class ExternalResource {
private final String id = UUID.randomUUID().toString().substring(0, 8);
public ExternalResource() {
System.out.println("ExternalResource constructor: created with id=" + id);
}
@PostConstruct
public void init() {
System.out.println("ExternalResource @PostConstruct: ready to serve requests.");
}
@PreDestroy
public void close() {
System.out.println("ExternalResource @PreDestroy: closing resource id=" + id);
}
public String getId() {
return id;
}
}
class PrototypeBean {
private final String id = UUID.randomUUID().toString().substring(0, 8);
public PrototypeBean() {
System.out.println("PrototypeBean constructor: new instance id=" + id);
}
@PostConstruct
public void init() {
System.out.println("PrototypeBean @PostConstruct: runs for each new instance.");
}
@PreDestroy
public void destroy() {
// This usually will NOT print on context shutdown for prototype beans.
System.out.println("PrototypeBean @PreDestroy: this is not called automatically for prototype scope.");
}
public String getId() {
return id;
}
}Follow-up & Tricky Questions:
BeanPostProcessor before-initialization hooks, then init callbacks, then after-initialization hooks, and finally destroy callbacks on shutdown.@PostConstruct and afterPropertiesSet()? Both run after dependency injection, but @PostConstruct is annotation-based and cleaner, while afterPropertiesSet() ties the class to Spring’s InitializingBean interface.BeanPostProcessor used for? It lets Spring intercept beans before and after initialization, which is how many framework features work, including proxy creation for AOP.lazy beans? Lazy singleton beans are not created at startup; Spring waits until the first time they are requested, which can improve startup time but move failures later.ApplicationContext call destroy methods? On context close or JVM shutdown if shutdown hooks are enabled, but only for beans managed in scopes where Spring owns destruction, mainly singletons.@PreDestroy run for prototype beans? Usually no. That is a common interview trap: Spring does not track prototype destruction the way it tracks singleton shutdown.postProcessAfterInitialization(), so the final object seen by other beans is sometimes a wrapper around the original bean.Common Mistakes:
@PostConstruct. Correction: keep it lightweight; long remote calls can slow startup and break readiness probes.InitializingBean, DisposableBean, and processor hooks like BeanPostProcessor.Memory Hook: “Build it, wire it, bless it, use it, bury it.” Think of Spring as a stage manager: it builds the actor, hands them props, gives the cue, lets them perform, and cleans the stage afterward.
Cheat Sheet:
ApplicationContext.@PostConstruct and afterPropertiesSet() are init hooks; @PreDestroy and destroy() are cleanup hooks.BeanPostProcessor is the hook behind many Spring features, including proxies.Practice Tasks:
@Lazy and observe that creation moves from startup to first access.BeanPostProcessor with one that returns a proxy-like wrapper and notice how after-initialization can alter the final bean reference.