The bean lifecycle is where a Spring Boot interview separates people who wired up beans from people who understand the container that manages them. The questions are precise: name the callbacks, order them, and explain who invokes them and when. This set covers the questions asked across intermediate rounds — the full init-and-destroy sequence, the annotation versus interface choices, BeanPostProcessor, and the prototype trap — each with a spoken answer and the follow-up interviewers reach for.
Why interviewers ask about the bean lifecycle
Every Spring Boot application is a graph of beans that the container creates, wires, initializes and eventually destroys. Understanding that sequence is what lets you place initialization logic correctly, release resources cleanly, and reason about why a @Transactional proxy or an @Autowired field works at all. Interviewers use lifecycle questions because the answer is verifiable to the exact step — you either know the order or you don't.
Q1. Walk me through the full Spring bean lifecycle.
The container instantiates the bean, populates its properties and injects dependencies, invokes any Aware callbacks, runs BeanPostProcessor.postProcessBeforeInitialization, then the initialization callbacks in order — @PostConstruct, InitializingBean.afterPropertiesSet(), the custom init-method — then BeanPostProcessor.postProcessAfterInitialization. The bean is now ready for use. On context shutdown it runs @PreDestroy, DisposableBean.destroy(), then the custom destroy-method.
The mental model is two phases with a "ready" state between them. Creation covers instantiation through the after-initialization post-processor; destruction covers the shutdown callbacks. The three initialization hooks all fire after injection, which is precisely why @PostConstruct is the safe place to validate that required dependencies arrived.
@Component
class ReportService implements InitializingBean, DisposableBean {
@PostConstruct
void postConstruct() { System.out.println("1. @PostConstruct"); }
@Override
public void afterPropertiesSet() { System.out.println("2. afterPropertiesSet"); }
@Override
public void destroy() { System.out.println("3. DisposableBean.destroy"); }
}
Interview note: Follow-up: "if a bean has
@PostConstruct,afterPropertiesSet()and aninitMethod, in what order do they run?" That exact order — annotation, interface, then custom method. Reciting it confidently is the whole point of the question.
Q2. What do @PostConstruct and @PreDestroy do, and which package are they in?
@PostConstruct marks a method to run once, after the bean is fully constructed and injected; @PreDestroy marks a method to run once, just before the bean is destroyed. In Spring Boot 3 / Spring 6 they live in jakarta.annotation, not javax.annotation — the Jakarta EE namespace migration moved them.
Because they are plain annotations, the class stays free of Spring-specific interfaces, which is why they are the modern default for open/close-style lifecycle logic — opening a connection pool, starting a scheduler, warming a cache on the way up, and releasing those resources on the way down.
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Component
class CacheWarmer {
@PostConstruct
void warm() { /* preload cache after dependencies are ready */ }
@PreDestroy
void flush() { /* release resources before shutdown */ }
}
Interview note: Trap: "why did old
@PostConstructcode stop compiling after a Spring Boot 3 upgrade?" The import moved fromjavax.annotationtojakarta.annotation. Knowing the namespace switch signals you have actually done a 2-to-3 migration.
Q3. InitializingBean and DisposableBean vs the annotations — which do you use?
InitializingBean forces afterPropertiesSet() and DisposableBean forces destroy(). They work, but they couple your class to Spring interfaces, so the preferred choices are @PostConstruct/@PreDestroy for your own classes and @Bean(initMethod, destroyMethod) for third-party classes you cannot annotate.
The interface approach is marginally faster because there is no reflection to find the annotated method, but that difference is irrelevant at startup and never worth the coupling. The useful thing to know is that all three mechanisms can coexist on one bean, and Spring runs them in the fixed order from Q1.
Interview note: Follow-up: "when would you actually reach for
@Bean(initMethod=...)?" When the class comes from a library and you cannot add annotations to its source — you declare the init and destroy method names on the@Beandefinition instead.
Q4. How do you configure init and destroy methods on a @Bean?
On a @Bean method you set initMethod and destroyMethod to the names of methods on the returned type. Spring calls them at the same lifecycle points as afterPropertiesSet() and destroy(), without the class implementing any Spring interface.
This is the clean way to manage the lifecycle of external types — a client, a pool, a native resource — inside a @Configuration class.
@Configuration
class PoolConfig {
@Bean(initMethod = "start", destroyMethod = "shutdown")
ConnectionPool connectionPool() {
return new ConnectionPool(); // start()/shutdown() are its own methods
}
}
Interview note: Trap: "what does
destroyMethoddefault to for@Bean?" It infers a public no-argclose()orshutdown()method automatically. SetdestroyMethod = ""to disable that inference when you do not want Spring callingclose().
Q5. What is a BeanPostProcessor and how is it different from a BeanFactoryPostProcessor?
A BeanPostProcessor intercepts every bean instance between injection and readiness, with postProcessBeforeInitialization and postProcessAfterInitialization hooks — it operates on constructed objects. A BeanFactoryPostProcessor runs earlier and operates on bean definitions, before any bean is instantiated, letting you modify configuration metadata.
BeanPostProcessor is the engine behind much of Spring itself: @Autowired and @PostConstruct are handled by post-processors, and AOP and @Transactional proxies are created by wrapping the target in postProcessAfterInitialization.
@Component
class TimingPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String name) {
// e.g. wrap bean in a proxy, or just observe every initialized bean
return bean;
}
}
Interview note: Follow-up: "how does
@Transactionalactually get applied?" A BeanPostProcessor returns a proxy in place of your bean after initialization, and the proxy opens and commits the transaction around your method. That is why self-invocation inside the same class bypasses it.
Q6. Where do Aware interface callbacks fit in the lifecycle?
Aware callbacks fire after dependency injection but before the initialization callbacks. BeanNameAware.setBeanName, BeanFactoryAware.setBeanFactory and ApplicationContextAware.setApplicationContext hand the bean pieces of container infrastructure it asked for.
They exist so a bean can learn its own name or reach the context, but they couple your code to Spring, so favour injecting what you need (for example ApplicationContext or Environment) over implementing the Aware interface. When you see them in an interview, the expected point is ordering: Aware comes after injection, before @PostConstruct.
Q7. Why don't destroy callbacks run for prototype beans?
Because Spring manages a prototype bean only until it hands the instance to the requester. It keeps no reference afterward, so it has nothing to call @PreDestroy or destroy() on. The container fully manages the creation lifecycle of a prototype but not its destruction — cleanup is the caller's responsibility.
This is one of the most reliable lifecycle traps. Singletons live in the context and get their destroy callbacks on shutdown; prototypes are created fresh on each request and then forgotten. If a prototype holds a resource that must be released, you release it yourself or wrap acquisition so the caller controls teardown.
@Component
@Scope("prototype")
class Task {
@PreDestroy
void cleanup() { /* NEVER called by the container for a prototype */ }
}
Interview note: Trap: "how would you get destroy logic to run for a prototype anyway?" Call it explicitly, or register a custom
DisposableBeanAdapter/destruction callback — but the honest answer interviewers want first is "the container will not do it for you."
Q8. What is the lifecycle difference between a singleton and a prototype bean?
A singleton is created once, eagerly by default at context startup, cached, and destroyed on context shutdown — so it receives both init and destroy callbacks. A prototype is created lazily, once per request for the bean, receives init callbacks, and never receives destroy callbacks.
This connects the lifecycle to scope, which is why the two topics are usually asked together. The eager-versus-lazy detail matters too: singletons are instantiated up front unless marked @Lazy, so a broken singleton fails fast at startup rather than on first use.
Interview note: Follow-up: "does
@Lazychange which callbacks a singleton gets?" No — it only delays creation until first access. The init callbacks still run at creation time, just later; destroy still runs on shutdown.
Q9. When exactly does @PostConstruct run relative to dependency injection?
After all dependencies are injected and after any Aware callbacks, but before the bean is exposed for use. That guarantee is why @PostConstruct is the correct place to validate configuration or derive state that depends on injected collaborators — everything it needs is already wired.
Putting that logic in the constructor is the common mistake: field and setter injection have not happened yet when the constructor runs, so injected fields are still null. Constructor injection avoids that, but even then, cross-dependency initialization belongs in @PostConstruct where the whole graph is settled.
@Service
class PricingService {
@Autowired private RateTable rates;
@PostConstruct
void validate() {
Objects.requireNonNull(rates, "rates must be injected"); // rates is ready here
}
}
Interview note: Trap: "why is
@Autowiredfield access null in the constructor?" The object must exist before Spring can inject into its fields, so the constructor runs first with fields unset. This is the standard argument for constructor injection.
How to prepare
Draw the lifecycle once, end to end, and annotate each arrow with who calls it — the container, a post-processor, or your code. Then build the tiny demo from Q1 with a @PostConstruct, an afterPropertiesSet() and an initMethod on one bean and run it: seeing the print order land in the exact sequence fixes it permanently, and it is the single most-asked lifecycle question. Do the same for the prototype in Q7 and watch @PreDestroy never fire.
Work through the container fundamentals on the Spring Boot learning path, then connect this page to bean scopes and dependency injection, since lifecycle, scope and injection are almost always probed as one line of questioning. A focused mock interview on the container internals is the fastest way to find where your explanation runs out and push it one callback deeper.
Frequently Asked Questions
What is the correct order of Spring bean lifecycle callbacks?
What is the difference between @PostConstruct and InitializingBean?
Why don't destroy callbacks run for prototype beans?
What is a BeanPostProcessor used for?
What are Aware interfaces in Spring?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Discover CodeBegun's Java Full Stack track

