Spring Boot Bean Lifecycle Trivia
Four questions on Spring's bean lifecycle: scopes, init / destroy callbacks, circular dependencies, and conditional beans. Aimed at Java backend candidates who keep getting tripped up by the container.
By CodeSnatch
April 20, 2026
·
Updated May 20, 2026
1,159 views
8
4.5 (12)
A service is annotated @Service and injected into a controller. The team wants a new instance per HTTP request instead of the default singleton. Show two ways to do it and explain the trap when a singleton injects a request-scoped bean.
Examples
Example 1:
Input: CartService scoped request with proxyMode = TARGET_CLASS, injected into singleton CartController; two GET /checkout requests
Output: two distinct CartService instances
Explanation: The proxy resolves the actual bean from the current request scope on each access.Example 2:
Input: Inject request-scoped CartService directly without proxyMode into a singleton
Output: BeanCreationException at startup
Explanation: Spring tries to resolve the dependency at context startup when no request is active.// Conceptual: in Spring, this @Service is a singleton by default.
// @Service
class CartService {
public String checkout() { return "ok"; }
}
public class Main {
public static void main(String[] args) {
CartService svc = new CartService();
System.out.println(svc.checkout());
}
}Where in the bean lifecycle do @PostConstruct, InitializingBean.afterPropertiesSet, and @Bean(initMethod = ...) run? Order them and explain which one to actually use.
Examples
Example 1:
Input: CacheWarmer registered via @Bean(initMethod = "customInit") implements InitializingBean and has @PostConstruct
Output: prints '1: postConstruct', '2: afterPropertiesSet', '3: customInit'
Explanation: Spring runs JSR-250 annotations, then InitializingBean, then the configured initMethod, in that order.Example 2:
Input: Context shutdown for the same bean with @PreDestroy, DisposableBean, and destroyMethod
Output: callbacks fire in reverse: @PreDestroy, then DisposableBean.destroy, then destroyMethod
Explanation: Shutdown mirrors initialization order in reverse.// Conceptual: implements InitializingBean and uses @PostConstruct.
// Imports like jakarta.annotation.PostConstruct and
// org.springframework.beans.factory.InitializingBean would be present
// in a real Spring project.
class CacheWarmer {
public void postConstruct() { System.out.println("postConstruct"); }
public void afterPropertiesSet() { System.out.println("afterPropertiesSet"); }
public void customInit() { System.out.println("customInit"); }
}
public class Main {
public static void main(String[] args) {
CacheWarmer w = new CacheWarmer();
w.postConstruct();
w.afterPropertiesSet();
w.customInit();
}
}Two beans depend on each other via constructor injection and Spring fails to start with BeanCurrentlyInCreationException. The team's first instinct is @Lazy. What does @Lazy actually do here, and what is the structural fix the reviewer wants?
Examples
Example 1:
Input: UserService and NotificationService constructor-inject each other
Output: context startup fails with BeanCurrentlyInCreationException
Explanation: Constructor injection cannot resolve a cycle because neither bean can be constructed before the other.Example 2:
Input: Replace direct dependency with ApplicationEventPublisher in UserService and @EventListener(UserCreated.class) in NotificationService
Output: context starts
Explanation: The event abstraction breaks the cycle structurally: neither service depends on the other directly.// Conceptual: two @Component classes mutually constructor-inject each other.
// In a real Spring project, this fails with BeanCurrentlyInCreationException.
class UserService {
private final NotificationService notifications;
UserService(NotificationService n) { this.notifications = n; }
}
class NotificationService {
private final UserService users;
NotificationService(UserService u) { this.users = u; }
}
public class Main {
public static void main(String[] args) {
// We can't actually construct either side here without rewriting,
// which is the whole point: the cycle is structural.
System.out.println("Direct cycle: UserService <-> NotificationService");
}
}A @Bean definition is wrapped in @ConditionalOnProperty(name = "feature.search.enabled", havingValue = "true"). The property is absent in application.yml. Is the bean created, and what is the difference between havingValue and matchIfMissing?
Examples
Example 1:
Input: application.yml missing feature.search.enabled; bean has @ConditionalOnProperty(name = "feature.search.enabled", havingValue = "true")
Output: SearchClient bean is NOT registered
Explanation: Absence of the property fails the condition because matchIfMissing defaults to false.Example 2:
Input: Add feature.search.enabled: true to YAML
Output: SearchClient bean is registered
Explanation: Property value matches havingValue, so the condition passes.Example 3:
Input: Set matchIfMissing = true and leave the property unset
Output: SearchClient bean is registered
Explanation: matchIfMissing toggles what happens when the property is absent entirely.// Conceptual: in a real Spring Boot project these imports are:
// org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
// org.springframework.context.annotation.{Bean, Configuration}
// We simulate the conditional resolution inline so the snippet runs.
class SearchConfig {
// @Bean
// @ConditionalOnProperty(name = "feature.search.enabled", havingValue = "true")
SearchClient searchClient() { return new SearchClient(); }
}
class SearchClient {}
public class Main {
public static void main(String[] args) {
// Absent property + no matchIfMissing => bean NOT created.
String value = null;
boolean matchIfMissing = false;
boolean enabled = (value != null && value.equals("true")) || (value == null && matchIfMissing);
System.out.println("SearchClient registered: " + enabled);
}
}