What does @Lazy do when placed on an injection point (not a bean definition), and how does the resulting proxy behave? When is it the right tool?
answer
- @Lazy on injection point → lazy-resolution proxy
- JDK proxy (interface) / CGLIB (class)
- Resolves real bean on FIRST method call
- Breaks constructor cycles / defers costly init / scope bridging
- Masks startup errors; final classes break CGLIB; single-target vs ObjectProvider
basics
~20 s@Lazy on an injection point injects a lazy-resolution proxy instead of the real bean. Spring resolves the actual bean only on the first method call through the proxy. It's used to break circular dependencies, defer expensive initialization, or inject a shorter-scoped bean.
solid answer
~50 sOn an injection point, @Lazy tells Spring to inject a lazy-resolution proxy rather than eagerly resolving the target bean at wiring time. The proxy (JDK dynamic proxy for interfaces, CGLIB for classes) holds no real bean until a method is invoked; on first call it looks the bean up from the container and delegates. This differs from @Lazy on a @Bean/@Component, which merely makes that bean initialize lazily (still one shared instance). Use injection-point @Lazy to: break a circular constructor dependency (the proxy defers resolution past construction), avoid triggering expensive/slow bean init at startup, or inject a narrower-scoped bean (e.g. request-scoped) into a singleton so each call resolves the current-scope instance. Caveats: the proxy adds indirection and only works cleanly for proxyable types; it can mask true startup errors until first use; and it is a single-target handle, unlike ObjectProvider's optional/stream API.
code
java · 17 lines// Breaking a circular constructor dependency with @Lazy
@Service
class OrderService {
private final InventoryService inventory;
OrderService(@Lazy InventoryService inventory) { // proxy injected
this.inventory = inventory;
}
void place(Order o) { inventory.reserve(o); } // real bean resolved here
}
@Service
class InventoryService {
private final OrderService orders;
InventoryService(OrderService orders) { this.orders = orders; }
void reserve(Order o) { /* ... */ }
}
// Without @Lazy this pair throws BeanCurrentlyInCreationException at startup.go deeper
Know @Lazy can defer creation and help with circular dependencies.
Distinguish @Lazy on a bean (lazy init) vs on an injection point (proxy), and name the cycle-breaking use.
Explain proxy type (JDK/CGLIB), first-call resolution, and scope-bridging plus the masked-error caveat.
Weigh @Lazy vs ObjectProvider vs scoped proxies, discuss proxyability constraints, fail-fast trade-offs, and immutable constructor injection preservation.
## Two very different meanings of @Lazy `@org.springframework.context.annotation.Lazy` behaves differently depending on **where** you put it: 1. **On a bean definition** (`@Component` / `@Bean` method): the bean is **lazily initialized** — not created at context startup, but on first request. There is still exactly **one** shared singleton instance. 2. **On an injection point** (constructor param, field, setter, alongside `@Autowired`): Spring injects a **lazy-resolution proxy** instead of the real bean. This is the meaning relevant here. ## The injection-point proxy When you annotate an injection point with `@Lazy`, Spring's `ContextAnnotationAutowireCandidateResolver` builds a **proxy** standing in for the dependency: - **JDK dynamic proxy** if the dependency type is an interface. - **CGLIB subclass proxy** if it's a concrete class (requires a non-final class / methods). The proxy holds no reference to the actual bean at wiring time. On the **first method invocation** through the proxy, it resolves the target bean from the `ApplicationContext` and delegates; subsequent calls re-resolve according to the target's scope (for a singleton, the same instance; for a shorter scope, the current-scope instance). ```java @Service class A { private final B b; A(@Lazy B b) { this.b = b; } // b is a proxy; real B fetched on first use void go() { b.work(); } // <-- resolution happens here } ``` ## Primary use-cases ### 1. Breaking circular dependencies If A needs B and B needs A via **constructor** injection, Spring cannot instantiate either first → `BeanCurrentlyInCreationException`. Marking one side `@Lazy` injects a proxy, so that constructor completes without the real bean; the cycle is broken because actual resolution is deferred until after both beans exist. (Field/setter injection can also break cycles, but constructor injection + `@Lazy` keeps immutability.) ### 2. Deferring expensive initialization If a collaborator is costly to build (large cache warm-up, remote connection) and rarely used, `@Lazy` avoids paying that cost at startup — the bean is created only when first actually invoked. ### 3. Injecting a shorter-scoped bean into a wider-scoped one Injecting a `request`- or `session`-scoped bean directly into a **singleton** captures a stale instance. A `@Lazy` proxy (or a scoped proxy) re-resolves on each call so the singleton always talks to the correct current-scope instance. (Dedicated scoped proxies via `proxyMode` are the more explicit tool, but `@Lazy` achieves deferred per-call resolution too.) ## Caveats & gotchas - **Masked errors:** because resolution is deferred, a genuinely missing/broken dependency surfaces only at **first use**, not at startup — you lose fail-fast behavior. - **Proxyability:** CGLIB proxies need a non-final class and a default/accessible constructor; `final` classes or `final` methods break class proxying. `equals`/`hashCode`/identity semantics can surprise. - **Not the same as lazy bean init:** `@Lazy` on the target `@Bean` doesn't create a proxy at injection points; it just delays that bean's construction. Combine deliberately. - **Single target only:** `@Lazy` gives one proxied dependency. If you need optionality, defaults, uniqueness handling, or streaming over many candidates, `ObjectProvider<T>` is the richer tool. - **Prototype interaction:** a `@Lazy` proxy over a prototype resolves a new instance per... actually delegates each call to a fresh lookup only if you re-resolve — for reliably fresh prototypes prefer `ObjectProvider.getObject()` per call. ## Choosing between @Lazy and ObjectProvider - **@Lazy** — transparent proxy, minimal code change, good for breaking cycles and deferring init when you want the dependency to *look* like a normal `T`. - **ObjectProvider** — explicit handle when you need optional/unique/default/stream semantics or guaranteed fresh prototype instances. Both defer resolution; ObjectProvider makes the deferral explicit at the call site.
- What's the difference between @Lazy on a @Component class versus @Lazy on a constructor parameter?On the class it makes that bean initialize lazily (still one shared singleton, created on first request). On the constructor parameter it injects a lazy-resolution proxy at the injection point, deferring resolution of the dependency to first method call — used to break cycles or defer/scope-bridge.
- Why can @Lazy hide problems compared to eager injection?Resolution is deferred to first use, so a missing or misconfigured dependency throws at runtime on first invocation rather than failing fast at context startup, weakening fail-fast guarantees.
- When would you prefer ObjectProvider over @Lazy for deferred resolution?When you need optionality (getIfAvailable), ambiguity-safe lookup (getIfUnique), defaults, streaming over many candidates, or reliably fresh prototype instances — ObjectProvider exposes these explicitly, whereas @Lazy is a single transparent proxy.
saying these in an interview costs you the question
- Saying @Lazy on an injection point just delays that bean's init (that's @Lazy on the bean)
- Claiming the @Lazy proxy resolves the bean at wiring time
- Thinking @Lazy works on final classes/methods without issue (CGLIB fails)
- Believing @Lazy preserves fail-fast startup validation