Skip to content
Spring Boot sb core 3 min read

Bean Scopes

A bean’s scope defines how many instances the IoC container creates and how long each one lives. By default Spring creates a single shared instance per container, but for stateful or web-bound beans you can ask for a new instance per injection, per HTTP request, or per session. Choosing the right scope is essential for correctness and thread safety.

The available scopes

Spring ships six built-in scopes. The last four are only available in a web-aware application context.

ScopeInstances createdLifetimeWeb only
singleton (default)One per containerEntire applicationNo
prototypeOne per injection / lookupUntil garbage collectedNo
requestOne per HTTP requestDuration of the requestYes
sessionOne per HTTP sessionDuration of the sessionYes
applicationOne per ServletContextEntire web applicationYes
websocketOne per WebSocket sessionDuration of the WebSocketYes

Note: application scope differs subtly from singleton. A singleton is one-per-ApplicationContext; an application-scoped bean is one-per-ServletContext, which can span multiple contexts in the same servlet container.

Singleton — the default

Every bean is a singleton unless you say otherwise. One instance is created eagerly at startup and shared everywhere it is injected. This is efficient but demands that the bean be stateless and thread-safe, because all threads share it.

@Service
public class PricingService {
    // no mutable instance fields — safe to share across threads
    public double withTax(double amount) {
        return amount * 1.20;
    }
}

Prototype — a fresh instance every time

A prototype bean is created anew on each injection point or getBean call. Use it for stateful, short-lived helpers.

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // or @Scope("prototype")
public class ReportBuilder {
    private final List<String> lines = new ArrayList<>();

    public void addLine(String line) {
        lines.add(line);
    }
}

Warning: The container does not run destruction callbacks (@PreDestroy) for prototype beans — it hands the instance off and forgets it. Release resources yourself. See Beans & Lifecycle.

Web scopes

The web scopes bind a bean’s lifetime to the current HTTP interaction. They are declared with @Scope or the convenience annotations @RequestScope, @SessionScope, and @ApplicationScope.

import org.springframework.web.context.annotation.RequestScope;
import org.springframework.stereotype.Component;

@Component
@RequestScope
public class RequestContext {
    private String correlationId;
    // a fresh instance is created for each incoming HTTP request
}

Injecting a shorter-lived bean into a singleton

This is the classic scoping pitfall. A singleton is created once at startup, so if you inject a request- or prototype-scoped bean directly, the singleton captures one instance forever — defeating the narrower scope.

The fix is a scoped proxy. Spring injects a lazy proxy into the singleton; on each method call the proxy resolves the correct instance for the current request or lookup.

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.stereotype.Component;

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
       proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
    private String correlationId;

    public String getCorrelationId() {
        return correlationId;
    }

    public void setCorrelationId(String id) {
        this.correlationId = id;
    }
}

@Service
public class AuditService {

    private final RequestContext requestContext; // actually a proxy

    public AuditService(RequestContext requestContext) {
        this.requestContext = requestContext;
    }

    public void record(String action) {
        // proxy delegates to the request-scoped instance for THIS request
        System.out.println(requestContext.getCorrelationId() + ": " + action);
    }
}

ScopedProxyMode.TARGET_CLASS uses CGLIB to proxy the concrete class. Use ScopedProxyMode.INTERFACES instead when the bean is injected through an interface.

Output: (two concurrent requests sharing the singleton AuditService)

req-abc-111: login
req-xyz-222: login

Each request sees its own correlationId even though AuditService is a single shared instance.

Tip: @RequestScope and @SessionScope default to ScopedProxyMode.TARGET_CLASS already, so injecting them into singletons “just works” without specifying proxyMode explicitly.

Choosing a scope

  • Default to singleton and keep the bean stateless — this covers the vast majority of services, repositories, and controllers.
  • Use prototype only for genuinely stateful, throwaway objects, and manage their cleanup yourself.
  • Use request / session for per-user web state, and remember to add a scoped proxy when injecting them into singletons.
  • Reach for application / websocket rarely, for state tied to the servlet context or a socket session.
Last updated June 13, 2026
Was this helpful?