skip to content

You inject a @RequestScope bean into a @RestController. Is a scoped proxy required, and what determines the answer?

level: middleimportance: should knowfreq 40%

answer

  1. controllers are singletons by default
  2. shorter-into-longer lifetime → proxy needed
  3. @RequestScope defaults to TARGET_CLASS → just works
  4. plain @Scope("request") = proxyMode NO → fails in singleton
  5. controllers must stay stateless

basics

~20 s

It 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 s

The 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
java
@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

for a junior

Know controllers are singletons, so a proxy is generally needed.

for a middle

State the lifetime-mismatch rule and that @RequestScope defaults to TARGET_CLASS.

for a senior

Explain the no-proxy cases (equal scope, method param, ObjectProvider) and concurrency/statelessness.

for a principal

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

context