skip to content

Contrast full-mode vs lite-mode @Configuration classes and explain the native-image implications of each.

level: seniorimportance: should knowfreq 40%

answer

  1. full = CGLIB proxy, sibling @Bean call → singleton
  2. lite = no proxy, sibling call → new instance
  3. proxyBeanMethods=false is native-safe
  4. Boot auto-config is lite mode
  5. lite mode: inject via method params, not sibling calls

basics

~20 s

Full mode (proxyBeanMethods = true, default) CGLIB-proxies the config class so calling one @Bean method from another returns the shared singleton. Lite mode (false) skips the proxy — calls create new instances. Native image can't do the CGLIB proxy, so lite mode is native-friendly.

solid answer

~40 s

A `@Configuration` class defaults to full mode (`proxyBeanMethods = true`): Spring CGLIB-subclasses it so that an inter-bean call like `serviceA(repo())` inside the config routes `repo()` back through the container and returns the singleton, preserving bean semantics. That guarantee costs a CGLIB proxy — unsupported in GraalVM native image because it needs runtime subclass generation. Lite mode (`proxyBeanMethods = false`) drops the proxy: `@Bean` methods become plain factory methods, so calling one directly produces a *new* instance instead of the singleton. It's faster to start, no proxy, and native-safe — which is why Spring Boot's own auto-configuration uses it. The trade-off: in lite mode you must inject collaborators via method parameters rather than calling sibling `@Bean` methods, or you'll get duplicate instances.

code

java · 9 lines
java
// Lite mode done right: dependencies passed as parameters, no CGLIB needed
@Configuration(proxyBeanMethods = false)
class Cfg {
    @Bean Repo repo() { return new Repo(); }

    // WRONG in lite mode: a() { return new ServiceA(repo()); } -> new Repo each time
    @Bean ServiceA a(Repo repo) { return new ServiceA(repo); }   // shared singleton
    @Bean ServiceB b(Repo repo) { return new ServiceB(repo); }   // same singleton
}

go deeper

for a junior

Know full mode uses a proxy so shared @Bean methods return the singleton; lite mode doesn't.

for a middle

Explain proxyBeanMethods flag and the duplicate-bean pitfall in lite mode.

for a senior

Connect full-mode CGLIB to native incompatibility and justify lite-mode + parameter injection.

for a principal

Discuss AOT bean-registration transformation, Boot's lite auto-config policy, and startup/memory trade-offs at scale.

**What `@Configuration` proxying solves.** Consider: ```java @Configuration class Cfg { @Bean Repo repo() { return new Repo(); } @Bean ServiceA a() { return new ServiceA(repo()); } @Bean ServiceB b() { return new ServiceB(repo()); } } ``` You want `a` and `b` to share ONE `Repo` singleton. In **full mode** (`proxyBeanMethods = true`, the default), Spring wraps `Cfg` in a **CGLIB subclass**. The override intercepts calls to `repo()` and, instead of running the method body a second time, returns the cached singleton from the `BeanFactory`. So both services get the same `Repo`. **Lite mode.** With `@Configuration(proxyBeanMethods = false)` (or on `@Component`/`@Bean` methods outside a `@Configuration`), there is **no CGLIB proxy**. `repo()` is just a normal Java method: each direct call constructs a new `Repo`. To keep a single instance you must instead take it as a parameter so the container supplies the managed bean: ```java @Configuration(proxyBeanMethods = false) class Cfg { @Bean Repo repo() { return new Repo(); } @Bean ServiceA a(Repo repo) { return new ServiceA(repo); } @Bean ServiceB b(Repo repo) { return new ServiceB(repo); } } ``` **Native-image angle.** Full mode's CGLIB subclass is generated at runtime — forbidden by GraalVM's closed-world model. So full-mode config classes are a native hazard. Lite mode eliminates the proxy entirely, making it the native-friendly default. Spring's AOT engine also transforms configuration into explicit bean-registration code, further reducing reliance on config-class proxying, but the guidance remains: prefer `proxyBeanMethods = false` and pass dependencies as parameters. **Why Boot uses lite mode everywhere.** All `@AutoConfiguration` classes are effectively lite (they set `proxyBeanMethods = false`), which cuts startup time and memory (no CGLIB proxy per config) AND keeps auto-config native-compatible. **Gotchas.** - Silent bug: switching to lite mode while still calling sibling `@Bean` methods directly yields **duplicate beans** — no error, just two `Repo` instances. Always inject via parameters in lite mode. - Full mode also requires the config class to be non-final with non-final `@Bean` methods (CGLIB subclassing) — a Kotlin pitfall. - Lite-mode `@Bean` methods can't be `private`/`final` restrictions differ; keep them package/public and non-final for clarity. **When to use.** Default to lite mode for native builds and for the performance win; use full mode only if you deliberately rely on inter-bean method calls and are JVM-only. Even then, injecting parameters is the cleaner pattern.

  • In lite mode, how do you ensure two beans share the same singleton dependency?
    Declare the dependency as a `@Bean` method and inject it as a *method parameter* into the consuming `@Bean` methods, so the container supplies the managed singleton — never call the sibling `@Bean` method directly, which would build a new instance.
  • Why does Spring Boot mark auto-configuration classes as lite (proxyBeanMethods=false)?
    To skip per-config CGLIB proxies: it speeds up startup, lowers memory, and keeps auto-configuration compatible with GraalVM native image, which can't generate the CGLIB subclass at runtime.

saying these in an interview costs you the question

  • Claiming lite mode still returns the singleton on direct sibling @Bean calls (it returns a new instance)
  • Thinking full mode has no runtime cost (it creates a CGLIB proxy)
  • Assuming full-mode config classes work fine in native image

context