Explain full mode vs lite mode for @Bean methods. What does CGLIB enhancement of @Configuration classes actually do?
answer
- full = @Configuration + CGLIB subclass
- override routes @Bean call to cached singleton
- lite = plain @Component, no proxy, new object per call
- lite fix: inject via method parameter
- no final class / no private-or-final @Bean in full mode
basics
~20 sIn a @Configuration class (full mode), Spring CGLIB-subclasses it so calling one @Bean method from another returns the cached singleton, not a new object. @Bean methods in a plain class (lite mode) aren't intercepted, so each direct call runs the method again.
solid answer
~50 sA class annotated with @Configuration runs in full mode: at startup Spring generates a CGLIB subclass that overrides every @Bean method. When one @Bean method calls another (an inter-bean reference), the override intercepts the call and returns the already-registered singleton from the container instead of executing the method body again. This preserves singleton identity even for direct Java method calls. @Bean methods declared on a class that is NOT @Configuration (e.g. a plain @Component or a lite-mode class) run in lite mode: no CGLIB proxy, so a direct call to another @Bean method is a plain Java call that constructs a new object each time, breaking the singleton guarantee. That's why sharing a bean via method call requires full mode — or, in lite mode, you must inject the dependency as a method parameter instead.
code
java · 20 lines@Configuration // FULL mode: CGLIB-enhanced
class FullConfig {
@Bean ClientProps props() { return new ClientProps(); }
// Both a() and b() get the SAME props singleton,
// because props() is intercepted and returns the cached bean.
@Bean ServiceA a() { return new ServiceA(props()); }
@Bean ServiceB b() { return new ServiceB(props()); }
}
@Component // LITE mode: NO proxy
class LiteConfig {
@Bean ClientProps props() { return new ClientProps(); }
// BUG: direct props() call runs the body again -> two instances.
@Bean ServiceA a() { return new ServiceA(props()); }
// CORRECT for lite mode: inject the managed bean as a parameter.
@Bean ServiceB b(ClientProps props) { return new ServiceB(props); }
}go deeper
Know the headline: in @Configuration, calling one @Bean method from another gives you the same singleton; in a plain class it doesn't.
Explain CGLIB subclassing/override interception, the new-instance bug in lite mode, and the parameter-injection fix.
Discuss the final/private restrictions, Kotlin all-open plugin, startup cost, and when to deliberately choose lite mode.
Reason about how this affects auto-configuration design, proxy identity in AOP, and why the framework is moving toward proxyBeanMethods=false for startup/native-image benefits.
## Two modes for @Bean methods Spring distinguishes **full mode** and **lite mode** based on where `@Bean` methods are declared. ### Full mode — @Bean methods inside a @Configuration class When a class is annotated with `@Configuration` (and `proxyBeanMethods` is left at its default `true`), Spring **enhances** the class at container startup using **CGLIB** (a bytecode library that generates a runtime subclass). The subclass **overrides every `@Bean` method**. The override does NOT blindly run your method body; instead it asks the container: *has this bean already been created?* If yes, it returns the cached singleton; if no, it runs your method once, registers the result, and caches it. Why this matters: **inter-bean references**. Consider one `@Bean` method calling another directly in Java: ```java @Configuration class AppConfig { @Bean ClientProps props() { return new ClientProps(); } @Bean ServiceA a() { return new ServiceA(props()); } @Bean ServiceB b() { return new ServiceB(props()); } // same props()? } ``` In full mode, both `a()` and `b()` receive **the same** `ClientProps` singleton, because the CGLIB override of `props()` routes through the container. Without enhancement, `props()` is a normal Java call and would run twice, producing two different instances. ### Lite mode — @Bean methods outside a @Configuration class If you put `@Bean` methods on a class that is NOT `@Configuration` — a plain `@Component`, an interface default method, or any bean class — those methods run in **lite mode**. There is **no CGLIB subclass** and **no interception**. A direct call to another `@Bean` method is an ordinary Java method call that executes the body and returns a **new object every time**, so the singleton guarantee is lost for those inter-method calls. Spring still registers the *return value of the container-invoked method* as a bean, but self-invocation between methods is not managed. Lite mode is also faster to start and imposes fewer restrictions (the class need not be CGLIB-subclassable). Spring uses lite mode automatically when it detects `@Bean` methods on a non-`@Configuration` component. ## How to write correct lite-mode config In lite mode, **never call another @Bean method directly**. Instead, declare the dependency as a **method parameter** so the container injects the managed singleton: ```java @Component // lite mode class LiteConfig { @Bean ClientProps props() { return new ClientProps(); } @Bean ServiceA a(ClientProps props) { return new ServiceA(props); } // injected @Bean ServiceB b(ClientProps props) { return new ServiceB(props); } // same singleton } ``` Parameter injection works in BOTH modes and is the most robust style. ## Requirements & gotchas of full mode (CGLIB) - The `@Configuration` class **cannot be `final`**, and `@Bean` methods **cannot be `private` or `final`** — CGLIB must subclass the class and override the methods. Violations cause a startup error. - The class needs an accessible (non-arg-only-if-needed) constructor CGLIB can call. - Enhancement adds a small startup cost and produces a proxy subclass instance rather than your raw class. - Kotlin gotcha: Kotlin classes/methods are `final` by default; `@Configuration` classes work because the Spring Kotlin plugin (`kotlin-spring` / all-open) opens them — without it you get CGLIB errors. ## When to use which - **Full mode (default @Configuration):** you rely on inter-bean method calls, or you want the safety of singleton identity regardless of call style. This is the conventional default. - **Lite mode:** simple factory methods with no inter-bean calls, or when you deliberately want to avoid CGLIB (see `proxyBeanMethods=false`). Requires disciplined parameter injection.
- In lite mode, how do you make two @Bean methods share the same singleton dependency?Declare the dependency as a method parameter on each @Bean method so the container injects the same managed singleton, rather than calling the other @Bean method directly.
- Why can't a @Configuration class or its @Bean methods be final?Full mode enhances the class with a CGLIB-generated subclass that overrides the @Bean methods. A final class can't be subclassed and a final/private method can't be overridden, so Spring can't intercept the calls — it fails at startup.
saying these in an interview costs you the question
- Claiming inter-bean method calls always return the singleton — true only in full mode, not lite mode.
- Saying Spring uses dynamic (JDK interface) proxies for @Configuration — it uses CGLIB subclassing.
- Thinking lite mode beans aren't managed at all — they are; only direct method-to-method calls bypass the container.
- Believing method-parameter injection only works in full mode — it works in both, and is the safe style.