You inject a @RequestScope bean into a @RestController. Is a scoped proxy required, and what determines the answer?
answer
- controllers are singletons by default
- shorter-into-longer lifetime → proxy needed
- @RequestScope defaults to TARGET_CLASS → just works
- plain @Scope("request") = proxyMode NO → fails in singleton
- controllers must stay stateless
basics
~20 sIt depends on the controller's scope. A @RestController is a singleton by default, so injecting a request-scoped bean into it needs a scoped proxy. Only if the controller were itself request-scoped could you inject without a proxy.
solid answer
~40 sThe rule is about scope-lifetime mismatch, not about controllers specifically. Spring controllers are singletons by default, created once. Injecting a shorter-lived request-scoped bean into a singleton requires a scoped proxy (proxyMode = TARGET_CLASS), so each request routes through the proxy to the correct instance. @RequestScope sets TARGET_CLASS by default, so it usually just works. If instead you made the controller request-scoped too (or accessed the bean as a method parameter / via ObjectProvider), no proxy would be needed because both live for the same request. As a rule: injecting a shorter-lived bean into a longer-lived one needs a proxy; equal-or-narrower lifetimes don't.
code
java · 17 lines@Component
@RequestScope // proxyMode TARGET_CLASS by default
public class RequestTrace {
private final String id = UUID.randomUUID().toString();
public String id() { return id; }
}
@RestController // singleton — needs the proxy above
public class OrderController {
private final RequestTrace trace; // injected proxy
public OrderController(RequestTrace trace) { this.trace = trace; }
@GetMapping("/orders")
public String list() {
return "trace=" + trace.id(); // resolves current request's instance
}
}go deeper
Know controllers are singletons, so a proxy is generally needed.
State the lifetime-mismatch rule and that @RequestScope defaults to TARGET_CLASS.
Explain the no-proxy cases (equal scope, method param, ObjectProvider) and concurrency/statelessness.
Advise on per-request state patterns, proxy costs vs providers, and avoiding shared mutable controller state at scale.
## The general rule Whether you need a scoped proxy is governed entirely by the **relative lifetimes** of the injecting bean and the injected bean: - Injecting a **shorter-lived** bean into a **longer-lived** one → **needs a proxy** (or an `ObjectProvider`/`@Lookup`). - Injecting into an **equal-or-shorter-lived** bean → no proxy needed; direct injection is fine. ## Applied to controllers Spring MVC `@Controller`/`@RestController` beans are **singletons by default** — one instance handles all requests concurrently on different threads. That's why controllers must be **stateless**. Injecting a `@RequestScope` bean into such a singleton is the classic mismatch, so a scoped proxy is required. Fortunately `@RequestScope` (and `@SessionScope`, `@ApplicationScope`) default `proxyMode` to `TARGET_CLASS`, so the proxy is created automatically — the injection "just works" and each HTTP request transparently gets its own backing instance. ### When no proxy is needed - If the **controller itself** is annotated `@RequestScope`, both live one-per-request, so a plain `@Scope("request")` (proxyMode NO) target could be injected directly. (Request-scoped controllers are unusual.) - If you take the value as a **handler-method parameter** or resolve it via injected `ObjectProvider<T>`/`ObjectFactory<T>` at call time, no proxy is required because the lookup happens within the request. ## Concurrency note Because the controller singleton is shared across threads, never store per-request state in controller fields. A request-scoped collaborator (via proxy) is exactly the right tool to hold per-request state safely — the proxy dispatches to the current thread's instance. ## Gotchas - Forgetting that `@Scope("request")` **without** a proxyMode defaults to NO proxy — injecting that into a singleton controller fails at startup with 'No thread-bound request found' or a scope-resolution error. Use `@RequestScope` or add `proxyMode = TARGET_CLASS`. - The proxy is a CGLIB subclass, so the target can't be `final`. - Don't cache `proxy`-returned values across requests in singleton fields.
- If the controller itself were @RequestScope, would you still need proxyMode on the injected request-scoped bean?No. Both would live for exactly one request (equal lifetime), so a plain request-scoped bean can be injected directly without a proxy.
- What error typically appears if you inject @Scope("request") with proxyMode NO into a singleton?At creation Spring can't resolve the request-scoped target for the singleton — you get a BeanCreationException, often wrapping IllegalStateException 'No thread-bound request found' (no active request at singleton creation time).
saying these in an interview costs you the question
- Believing controllers are prototype/per-request by default
- Thinking @Scope("request") and @RequestScope behave identically (proxyMode differs)
- Storing per-request state directly in singleton controller fields