You inject a prototype bean into a singleton with plain @Autowired. Why do you keep getting the same prototype instance, and how do you fix it?
answer
- injection resolves ONCE at owner creation
- singleton created once -> prototype field frozen
- ObjectProvider.getObject() each use
- @Lookup method overridden by CGLIB
- scoped proxy = new target per method call
basics
~20 sInjection happens once, when the singleton is created, so the prototype is resolved a single time and reused forever. To get a fresh instance per use, ask the container each time — via ObjectProvider, @Lookup, JSR-330 Provider, or a scoped proxy.
solid answer
~40 sDependency injection resolves each injection point once, at the time the enclosing bean is instantiated. A singleton is created once, so its prototype-typed field is populated once — you hold that single prototype instance for the singleton's whole life, defeating the point of prototype scope. The container isn't consulted again on later use. The fix is to defer resolution to the moment of use: inject an ObjectProvider<T> (or JSR-330 Provider<T>) and call getObject()/get() each time you need a new instance; or use a @Lookup method that Spring overrides to return a fresh bean per call; or declare the prototype with a scoped proxy (@Scope(proxyMode = TARGET_CLASS)) so the injected object is a proxy that fetches a new target on each method call. ObjectProvider is the modern, Spring-native choice.
code
java · 13 lines@Component @Scope("prototype")
class Task { }
@Service
class Scheduler {
private final ObjectProvider<Task> tasks;
Scheduler(ObjectProvider<Task> tasks) { this.tasks = tasks; }
void run() {
Task fresh = tasks.getObject(); // distinct instance every call
fresh.execute();
}
}go deeper
Recognize the symptom: same prototype every time when injected into a singleton.
Explain why (one-time injection at owner creation) and name ObjectProvider / @Lookup as fixes.
Compare ObjectProvider vs Provider vs @Lookup vs scoped proxy, including the per-call instantiation caveat of the proxy.
Discuss the service-locator trade-off, testability, CGLIB constraints, and why ObjectProvider is the idiomatic default.
## The trap ```java @Component @Scope("prototype") class Task { } @Service class Scheduler { @Autowired Task task; // resolved ONCE void run() { use(task); } // same Task forever } ``` `Scheduler` is a singleton, created a single time at startup. During that one creation, Spring resolves the `Task` dependency **once**, gets one prototype instance, and assigns it to the field. Every subsequent `run()` uses that **same** object. The container is never asked again, so prototype scope is effectively neutralized. This is the single most common bean-scope interview trap. **Key mental model:** *scope governs how the container hands out beans; it does not make a field re-resolve.* Injection is a one-time event tied to the **owner's** creation, not to each access. ## Fix 1 — `ObjectProvider<T>` (preferred, Spring-native) ```java @Service class Scheduler { private final ObjectProvider<Task> taskProvider; Scheduler(ObjectProvider<Task> taskProvider) { this.taskProvider = taskProvider; } void run() { Task task = taskProvider.getObject(); // NEW prototype each call use(task); } } ``` `ObjectProvider` (an enhanced Spring type) is injected once, but calling `getObject()` re-queries the container, yielding a fresh prototype every time. It also offers `getIfAvailable()`, `getIfUnique()`, `ifAvailable(...)`, and stream methods — handy for optional/multiple beans. ## Fix 2 — JSR-330 `Provider<T>` ```java @Autowired private Provider<Task> taskProvider; // jakarta.inject.Provider Task t = taskProvider.get(); // fresh each call ``` Same idea, standard API, requires the `jakarta.inject` dependency. Less feature-rich than `ObjectProvider`. ## Fix 3 — Lookup method injection (`@Lookup`) ```java @Service abstract class Scheduler { void run() { use(createTask()); } @Lookup Task createTask() { return null; } // Spring overrides this } ``` Spring subclasses the bean via CGLIB and overrides the `@Lookup` method so each call returns a fresh container-provided bean. The method body is irrelevant (often `return null;`). Because it needs a runtime subclass, the class/method can't be `final`. ## Fix 4 — Scoped proxy ```java @Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) class Task { } @Service class Scheduler { @Autowired Task task; // injected object is a PROXY void run() { task.doWork(); } // proxy fetches a new target per call } ``` Spring injects a CGLIB proxy once; each *method invocation* on the proxy resolves a new prototype target. Caveat: a new instance is created per method call, which can be surprising for stateful prototypes (state doesn't persist across calls) and is the standard mechanism for request/session scopes rather than the usual choice for prototypes. ## Anti-patterns to avoid - **`ApplicationContextAware` + `getBean(Task.class)`** works but couples your code to the container (service-locator smell). `ObjectProvider` is the clean equivalent. - Assuming `@Scope("prototype")` alone is enough when injected into a singleton — it is not. ## Which to pick `ObjectProvider<T>` is the default modern recommendation: explicit, testable (you can pass a lambda-backed provider), no bytecode subclassing constraints. Use `@Lookup` in legacy code or when you want the abstract-method style. Use scoped proxies mainly for web scopes; for prototypes be aware of the per-call instantiation semantics.
- Why does @Lookup require the bean/method not to be final?Spring implements lookup-method injection by generating a CGLIB subclass that overrides the method. Final classes can't be subclassed and final methods can't be overridden, so both must be non-final.
- What's the behavioral catch with a prototype scoped proxy versus ObjectProvider?A scoped proxy creates a new target on each method invocation, so per-instance state never accumulates across calls. ObjectProvider gives you one explicit fresh instance per getObject() call that you can hold and mutate across several calls.
- Is calling applicationContext.getBean() inside the singleton a valid fix?It works, but it's a service-locator anti-pattern that couples the class to the container and hurts testability. ObjectProvider/Provider give the same 'ask per use' semantics without that coupling.
saying these in an interview costs you the question
- Believing @Scope("prototype") alone yields a new instance on each use inside a singleton.
- Saying the container re-injects the field on every method call.
- Reaching for ApplicationContextAware/getBean as the primary solution.
- Thinking constructor injection of the prototype (instead of field) fixes it — it doesn't; it's still resolved once.