Once you know that a Spring bean is a container-managed object, the next question is: how many of it exist, and how long does each live? That is what a bean scope decides. Pick the wrong scope and you either waste memory or, worse, share mutable state between users and create bugs that only appear under load.
This page walks through every scope Spring offers — singleton, prototype and the web scopes — with the reasoning for when each fits and the concurrency trap that catches almost everyone at least once.
Singleton: the default, one shared instance
Unless you say otherwise, every bean is a singleton: the container creates exactly one instance and hands that same object to everyone who depends on it.
import org.springframework.stereotype.Service;
@Service // no @Scope means singleton
public class TaxCalculator {
// one instance shared across the whole application
public double taxOn(double amount) {
return amount * 0.18;
}
}
Every class that injects TaxCalculator receives the identical object. For a stateless
service like this — it holds no per-user data, just logic — that is perfect and efficient.
This is why the beans you define following Spring beans
are singletons by default and why most of your services should stay that way.
The catch is right there in "shared": one instance, many threads. That leads directly to the most important warning on this page.
Common mistake: Putting mutable, request-specific state in a singleton bean's fields. Because every thread shares the one instance, two users' requests will stomp on each other's data. Keep singleton beans stateless — pass per-request data as method parameters, never store it in fields.
Prototype: a new instance every time
Set the scope to prototype and the container builds a brand-new instance on every request for that bean.
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ShoppingCart {
private final List<String> items = new ArrayList<>(); // per-instance state
public void add(String item) {
items.add(item);
}
}
Each time something asks the container for a ShoppingCart, it gets its own cart with its
own items list. This is exactly right for stateful objects that must not be shared. The
tradeoff is that Spring stops managing the object after creation — it runs init callbacks but
never destroy callbacks for prototypes, so any cleanup is on you. That lifecycle difference is
explained further in the bean lifecycle.
The singleton-holds-prototype trap
A subtle problem appears when you inject a prototype into a singleton. Because the singleton is built only once, its prototype dependency is injected only once too — so you get a single fixed instance, not a fresh one per call.
@Service // singleton
public class OrderProcessor {
// Injected ONCE when OrderProcessor is created — NOT a new cart per order!
private final ObjectProvider<ShoppingCart> cartProvider;
public OrderProcessor(ObjectProvider<ShoppingCart> cartProvider) {
this.cartProvider = cartProvider;
}
public void process() {
ShoppingCart freshCart = cartProvider.getObject(); // NOW you get a new one
// use freshCart for this order only
}
}
Injecting the ShoppingCart directly would freeze one instance for the singleton's whole
life. Injecting an ObjectProvider (or a scoped proxy) and calling getObject() each time
gives you the fresh prototype you actually wanted. This is a favourite interview question
because it reveals whether you have really used prototype scope or only read about it — see
the Spring Boot 2 years experience interview set.
Web scopes: request, session, application
In a web application the container becomes web-aware and three more scopes unlock, each tying a bean's life to an HTTP concept.
| Scope | Lives for | Typical use |
|---|---|---|
request |
One HTTP request | Per-request context, request-scoped data |
session |
One user session | A logged-in user's preferences, cart |
application |
The whole ServletContext | App-wide shared config |
import org.springframework.web.context.annotation.RequestScope;
@Component
@RequestScope // one instance per HTTP request
public class RequestAuditContext {
private String requestId;
private long startTime;
// safely holds data for THIS request only
}
A @RequestScope bean is created when a request begins and discarded when it ends, so
holding per-request state in its fields is safe — unlike doing the same in a singleton. Spring
provides @RequestScope, @SessionScope and @ApplicationScope as convenient shortcuts for
the corresponding @Scope values. These only apply once you are building web endpoints, the
kind you create in building a REST API with Spring Boot.
Why singleton is safe despite being shared
New learners often worry: if one singleton instance serves every request, surely two users' requests collide? The answer lies in where state lives. A singleton bean is shared, but the local variables inside its methods are not — each thread that calls a method gets its own stack frame with its own local variables and parameters.
@Service
public class InvoiceService {
// NO mutable fields — everything the method needs comes in as parameters
public Invoice generate(Order order, Customer customer) {
double subtotal = order.total(); // local: unique per thread/call
double tax = subtotal * 0.18; // local: unique per thread/call
return new Invoice(customer, subtotal, tax);
}
}
Because InvoiceService holds no mutable fields and keeps all per-request data in local
variables and parameters, a thousand threads can share the single instance safely. This is
the golden rule of singleton beans: no mutable shared state in fields. Follow it and the
default scope is both efficient and correct — which is exactly why Spring makes it the
default and why most services in the Spring beans guide
stay singleton.
Setting a scope explicitly
You have seen @Scope and the web-scope shortcuts, but it is worth collecting the ways to
declare a scope in one place, because interviewers sometimes ask you to write it.
// Long form, using the string constant
@Component
@Scope("prototype")
public class ReportBuilder { }
// Long form for a web scope, with a proxy so it can be injected into a singleton
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext { }
The proxyMode on the second example is the clean solution to the singleton-holds-shorter-
scope problem from earlier: Spring injects a proxy that, on each call, resolves the correct
request-scoped instance. It is an alternative to the ObjectProvider approach and the one you
will most often see in web code. Both exist because a longer-lived bean cannot hold a direct
reference to a shorter-lived one without help.
Choosing the right scope
The decision is almost always driven by state:
- Stateless logic (calculators, validators, most services) — singleton. One instance, shared, efficient.
- Per-user or per-operation state that must not leak — prototype, or a web scope if the state maps to a request or session.
- Web request/session data — the matching web scope, so lifetime is handled for you.
Start from singleton and only widen when you have a concrete reason. Reaching for prototype "just in case" usually signals state that should not be in a bean at all.
Pro tip: In interviews, tie scope back to threading: "Singleton is the default and it is shared across threads, so it must be stateless. When I need per-user state, I switch to prototype or a web scope." Connecting scope to thread-safety is exactly the depth interviewers want, and it links to the wider inversion of control model that the container is built on.
Scope in the wider container picture
It helps to place scope alongside the other bean concepts so they form one model rather than separate facts. Defining a bean decides that it exists; its scope decides how many exist and how long each lives; its lifecycle decides what happens to each instance from birth to death. All three are managed by the same inversion of control container, and all three are things you declare while the container does the work.
Scope specifically answers a question the container must resolve every time a dependency is requested: do I hand back an existing instance, or create a new one? Singleton says "reuse", prototype says "create fresh", and the web scopes say "reuse within this request or session, create fresh for the next". Once you see scope as the container's caching-and-lifetime policy for a bean, the annotations stop being trivia and become a design decision you make deliberately.
Bringing it together
A bean scope answers "how many, and for how long". Singleton — the default — gives one shared
instance and demands statelessness. Prototype gives a fresh instance per request but leaves
cleanup to you. The web scopes — request, session, application — tie a bean's life to HTTP
concepts and make per-request state safe. Set any of them with @Scope or the web-scope
shortcuts.
Most beans should stay singleton; reach for a wider scope only when state forces it, and remember the singleton-holds-prototype trap. From here, continue through the Spring Boot learning path into dependency injection and the bean lifecycle to complete your grip on how the container manages objects.
Frequently Asked Questions
What is the default scope of a Spring bean?
What is the difference between singleton and prototype scope?
What are the web scopes in Spring?
Why is a prototype bean not destroyed by Spring?
What happens if you inject a prototype bean into a singleton?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

