Bean Scopes
Bean Scopes
Every Spring bean has a scope — a rule that governs how many instances Spring creates and how long each instance lives. Choosing the wrong scope is one of the most common sources of subtle bugs in Spring applications, so understanding each scope deeply is non-negotiable for a working developer.
The Six Built-In Scopes
Spring ships with six built-in scopes. Two are always available (singleton and prototype); four require a web-aware ApplicationContext (request, session, application, and websocket). This lesson covers the four you will encounter daily.
Singleton — The Default Scope
When no scope is declared Spring uses singleton: the container creates exactly one instance of the bean per ApplicationContext, caches it, and injects that same reference into every class that depends on it. The instance is created when the context starts (eager initialisation) and destroyed when the context closes.
eurToUsd above can be read and written simultaneously. Design singletons to be stateless or effectively immutable — or synchronise explicitly. Most services (repositories, HTTP clients, calculators) are naturally stateless and are excellent singletons.
You can also declare scope explicitly for clarity:
Prototype — A New Instance Every Time
With prototype scope Spring creates a brand-new instance every time the bean is requested — whether via applicationContext.getBean() or via injection. Spring never caches prototype instances and never calls their @PreDestroy method; lifecycle cleanup is the caller's responsibility.
Each call to getBean(CsvReportBuilder.class) (or each injection point that requests one) receives its own clean buffer. Use prototype for beans that carry per-operation mutable state — builders, command objects, parsers — where sharing a single instance would cause data corruption.
ApplicationContext or use a lookup method (@Lookup) so the singleton fetches a fresh prototype on each use. You will see this pattern in the next lesson.
Request Scope — One Instance Per HTTP Request
In a web application the request scope creates one bean instance for the lifetime of a single HTTP request. The instance is created when the request arrives and discarded when the response is committed. This is safe to inject into singletons via a scoped proxy (Spring substitutes a proxy at compile time; at runtime the proxy delegates to the request-specific instance).
A singleton filter or interceptor populates RequestContext at the start of each request, and any downstream service can inject it to read the current user or correlation ID — without passing parameters through every method call. This is a clean alternative to ThreadLocal.
proxyMode = ScopedProxyMode.TARGET_CLASS when injecting a request- or session-scoped bean into a singleton. Without a proxy, Spring cannot create the singleton because the request-scoped bean does not yet exist at context startup — you will get a BeanCreationException.
Session Scope — One Instance Per HTTP Session
The session scope binds one bean instance to an HTTP session (the session created by the servlet container, identified by a cookie or URL rewrite). The instance persists across multiple requests from the same user until the session expires or is invalidated.
The controller that handles POST /cart/add injects ShoppingCart as a normal dependency. Spring's proxy transparently routes each call to the cart belonging to the current user's session.
java.io.Serializable and add a serialVersionUID if your application will ever run behind a load balancer.
Choosing the Right Scope — Decision Guide
- Stateless services, repositories, configuration, HTTP clients →
singleton. Thread-safe by design, cheapest option. - Stateful per-operation objects (builders, parsers, command objects) →
prototype. Each caller gets a clean slate. - Per-request cross-cutting data (correlation ID, audit context, current tenant) →
requestwith a scoped proxy. - Per-user state across multiple requests (shopping cart, wizard step, user preferences) →
sessionwith a scoped proxy.
Scoped Proxy Under the Hood
When you specify proxyMode = ScopedProxyMode.TARGET_CLASS, Spring uses CGLIB to generate a subclass of your bean at startup. That subclass is the actual singleton stored in the context. Every method call on it consults a ThreadLocal variable to locate the real instance bound to the current request or session, then forwards the call. The result: your singleton controller never holds a stale reference, and you write plain injection code without any threading boilerplate.
Summary
Bean scope determines instance lifetime. singleton is the right default for stateless collaborators; keep shared state out of singletons or synchronise it carefully. prototype delivers a fresh instance on each request and suits inherently stateful, short-lived objects. request and session scopes map Spring beans directly onto the HTTP request/session lifecycle and eliminate the need for ThreadLocal or manual session attribute management — but always pair them with ScopedProxyMode.TARGET_CLASS when injecting into singletons.