All articles
Spring CoreSpring Boot

Spring Bean Scopes Explained: Singleton, Prototype, and Web Scopes

November 27, 202510 min read

Spring manages bean instances through scopes — rules that determine how many instances are created and how long they live. Understanding scopes is essential for the VMware Spring Professional exam (2V0-72.22), where questions on singleton vs prototype behavior, scoped proxies, and web scopes appear regularly.

All 6 Spring Bean Scopes at a Glance

ScopeInstancesLifecycle ManagedAvailable In
singletonOne per ApplicationContextFull (creation + destruction)All contexts
prototypeNew instance per requestPartial (no destruction)All contexts
requestOne per HTTP requestFullWeb contexts only
sessionOne per HTTP sessionFullWeb contexts only
applicationOne per ServletContextFullWeb contexts only
websocketOne per WebSocket sessionFullWeb contexts only

The first two (singleton and prototype) are available in any Spring application. The remaining four require a web-aware ApplicationContext such as WebApplicationContext.


Singleton Scope (Default)

The singleton scope creates one shared instance per ApplicationContext. Every call to getBean() and every @Autowired injection returns the same object reference.

@Component
// @Scope("singleton") — this is the default, no annotation needed
public class OrderService {
    // One instance shared across the entire application
}

When to use singleton

Use for stateless services — repositories, service classes, configuration beans, and any component that does not hold per-user or per-request state.

Thread safety

Singleton beans are shared across all threads. Spring does not make singletons thread-safe automatically — that is your responsibility. If a singleton bean holds mutable state, concurrent access will cause bugs.

@Component
public class CounterService {
    private int count = 0; // NOT thread-safe in a singleton!
 
    public void increment() {
        count++; // Race condition under concurrent access
    }
}

To avoid issues: keep singletons stateless, or use AtomicInteger, synchronized, or ConcurrentHashMap for shared mutable state.

Eager vs lazy initialization

Singleton beans are created eagerly by default — at ApplicationContext startup. You can change this with @Lazy:

@Component
@Lazy
public class ExpensiveService {
    // Created only when first requested, not at startup
}

Exam tip: Singleton scope is per-ApplicationContext, not per-JVM. Two separate contexts in the same JVM (e.g. in a test) create two different singleton instances.


Prototype Scope

A new instance is created every time the bean is requested — via getBean(), @Autowired, or any other injection point.

@Component
@Scope("prototype")
public class ShoppingCart {
    private final List<Item> items = new ArrayList<>();
    // Each injection gets a fresh cart
}

When to use prototype

Use for stateful beans that should not be shared — shopping carts, request-specific command objects, or builders that accumulate state.

Spring does not manage prototype destruction

This is frequently tested on the exam. Spring creates the prototype bean and hands it off — but it does not track it afterward. This means:

  • @PreDestroy methods are never called on prototype beans
  • DisposableBean.destroy() is never called
  • You must clean up resources manually

Injecting prototype into singleton — the classic trap

If you inject a prototype bean into a singleton, the prototype is resolved once at singleton initialization — so you always get the same instance. This defeats the purpose of prototype scope.

@Component
public class SingletonService {
 
    @Autowired
    private ShoppingCart cart; // Always the SAME instance — bug!
}

Three solutions:

1. @Lookup method injection — Spring overrides the method at runtime to return a fresh prototype each time:

@Component
public abstract class SingletonService {
 
    @Lookup
    protected abstract ShoppingCart createCart();
 
    public void handleRequest() {
        ShoppingCart cart = createCart(); // New instance every call
    }
}

2. ObjectProvider<T> — a lazy, safe injection point:

@Component
public class SingletonService {
 
    private final ObjectProvider<ShoppingCart> cartProvider;
 
    public SingletonService(ObjectProvider<ShoppingCart> cartProvider) {
        this.cartProvider = cartProvider;
    }
 
    public void handleRequest() {
        ShoppingCart cart = cartProvider.getObject(); // New instance every call
    }
}

3. ApplicationContext.getBean() — works, but couples your code to the Spring API:

@Component
public class SingletonService {
 
    @Autowired
    private ApplicationContext context;
 
    public void handleRequest() {
        ShoppingCart cart = context.getBean(ShoppingCart.class); // New instance
    }
}

Exam tip: ObjectProvider is the recommended approach in modern Spring. @Lookup requires the class to be non-final (Spring creates a CGLIB subclass). getBean() is the least preferred option.


Request Scope

One bean instance per HTTP request. The bean is created when the request arrives and destroyed when the response is sent.

@Component
@RequestScope // shorthand for @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
    private String correlationId;
    private Instant startTime = Instant.now();
 
    // Each HTTP request gets its own instance
    // Automatically destroyed after the response is sent
}

Use for: per-request data — correlation IDs, request timing, audit information, user-specific request state.


Session Scope

One bean instance per HTTP session. Persists across multiple requests from the same user until the session expires or is invalidated.

@Component
@SessionScope // shorthand for @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserPreferences {
    private String theme = "dark";
    private String language = "en";
 
    // Same instance across all requests in the user's session
}

Use for: per-user state — preferences, shopping cart contents, wizard step tracking.


Application Scope

One instance per ServletContext — shared across all users and requests within the web application.

@Component
@ApplicationScope
public class GlobalRateLimiter {
    private final AtomicInteger requestCount = new AtomicInteger(0);
    // Shared across ALL users — similar to singleton but scoped to the ServletContext
}

The difference from singleton: a singleton is per-ApplicationContext, while application scope is per-ServletContext. In most Spring Boot apps, these are equivalent. The distinction matters when multiple ApplicationContext instances share one ServletContext (e.g. in a parent-child context setup).


WebSocket Scope

One bean instance per WebSocket session. The bean lives as long as the WebSocket connection is open.

@Component
@Scope("websocket")
public class ChatSession {
    private final List<String> messages = new ArrayList<>();
    // One instance per WebSocket connection
}

Use for: per-connection state in WebSocket applications — chat sessions, live dashboards, real-time collaboration.

Exam note: WebSocket scope is the least commonly tested of the six scopes. Know that it exists and requires a WebSocket-aware context.


Scoped Proxies

When you inject a shorter-lived scoped bean (like request) into a longer-lived bean (like singleton), Spring needs a scoped proxy to ensure each access resolves to the correct instance.

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
    private String userId;
}
 
@Component // singleton
public class AuditService {
 
    @Autowired
    private RequestContext requestContext;
    // This is actually a CGLIB proxy — each method call delegates
    // to the real RequestContext for the current HTTP request
}

Proxy modes

ModeBehavior
ScopedProxyMode.TARGET_CLASSCreates a CGLIB subclass proxy (default for @RequestScope, @SessionScope)
ScopedProxyMode.INTERFACESCreates a JDK dynamic proxy (bean must implement an interface)
ScopedProxyMode.NONo proxy — will fail if injected into a longer-lived scope

The convenience annotations @RequestScope, @SessionScope, and @ApplicationScope all default to ScopedProxyMode.TARGET_CLASS — so you typically don't need to configure this manually.

Exam tip: Without a scoped proxy, injecting a request-scoped bean into a singleton throws an exception at startup because there is no active HTTP request during singleton initialization.


Lifecycle and Destruction by Scope

Understanding which lifecycle callbacks Spring invokes per scope is a key exam topic:

Scope@PostConstruct called?@PreDestroy called?
singletonYes, at context startupYes, at context shutdown
prototypeYes, at each creationNo — Spring does not track prototype beans
requestYes, when request startsYes, when request ends
sessionYes, when session startsYes, when session is invalidated or expires
applicationYes, at context startupYes, at context shutdown
websocketYes, when connection opensYes, when connection closes

The prototype scope is the only one where Spring does not manage destruction. For all other scopes, both initialization and destruction callbacks are invoked.


Exam Quick Reference

Q: What is the default scope? singleton

Q: Which scopes require a web-aware ApplicationContext? request, session, application, websocket

Q: A prototype bean injected into a singleton bean — how many instances are created? Just one, at the time the singleton is initialized. Use @Lookup or ObjectProvider to get a new prototype each time.

Q: Does Spring call @PreDestroy on prototype beans? No. Spring does not manage the full lifecycle of prototype beans — only creation and initialization.

Q: What happens if you inject a request-scoped bean into a singleton without a scoped proxy? An exception is thrown at startup because no HTTP request is active when the singleton is being created.

Q: What is the difference between application scope and singleton scope? singleton is per-ApplicationContext, application is per-ServletContext. In most Spring Boot apps they behave the same, but the distinction matters in parent-child context configurations.


Frequently Asked Questions

What is the default bean scope in Spring?

The default scope is singleton — Spring creates one instance per ApplicationContext and shares it across all injection points. You do not need to declare it explicitly. This applies to both @Component-scanned beans and @Bean method definitions. To change the scope, use the @Scope annotation.

When should I use prototype scope instead of singleton?

Use prototype when the bean holds mutable state that should not be shared — for example, a shopping cart, a request-specific DTO builder, or a command object. If the bean is stateless (most service and repository classes), stick with singleton. Remember that Spring does not call @PreDestroy on prototype beans, so you must handle resource cleanup yourself.

What is a scoped proxy and when do I need one?

A scoped proxy is a wrapper that Spring creates around a shorter-lived scoped bean (like request or session) so it can be safely injected into a longer-lived bean (like singleton). Without a proxy, Spring would try to resolve the scoped bean at singleton creation time — when no HTTP request or session exists — and throw an exception. The @RequestScope and @SessionScope annotations enable proxying by default.

How do I inject a prototype bean into a singleton correctly?

The three recommended approaches are: ObjectProvider<T> (most modern and flexible), @Lookup method injection (Spring overrides the method via CGLIB), or ApplicationContext.getBean() (least preferred as it couples code to the Spring API). Direct @Autowired injection creates only one prototype instance, which is almost always a bug. See our Spring Bean Lifecycle guide for more on how injection timing affects bean behavior.

Test your knowledge with practice questions — 10 exam-style questions with detailed explanations

Spring Bean Lifecycle explained — full initialization and destruction callback order per scope

Spring Boot Auto-configuration explained — how @EnableAutoConfiguration discovers and loads beans

Get Full Access — 970+ Questions — full practice exam bank with detailed explanations

Practice This Topic

Reinforce what you've learned with free practice questions and detailed explanations.