Skip to content

Spring Boot: Bean Scopes | Singleton, Prototype, Request, Session Scopes with Examples

Real World Analogy

Imagine a coffee shop:

  • Singleton: There is one cash register. Every customer uses the same register.
  • Prototype: Every customer gets their own cup. A new cup is made for each order.
  • Request: A new receipt is printed for each purchase. Once the purchase is done, the receipt is discarded.
  • Session: A loyalty card is created when a customer joins. It stays active across multiple visits until they cancel their membership.

These four scenarios map directly to the four main Spring bean scopes.


Prerequisites

Bean scopes build directly on the bean lifecycle covered in the previous chapter. Recall the lifecycle:

  1. IoC container starts
  2. Bean discovery
  3. Bean construction
  4. Dependency injection
  5. @PostConstruct
  6. Bean in use
  7. @PreDestroy
  8. Bean destroyed

Understanding when a bean is constructed (step 3) is the key to understanding scopes. Different scopes control how many instances are created and when those instances are created.


The Five Bean Scopes

ScopeDescriptionInitialisation
SingletonOne instance per IoC containerEager (at startup)
PrototypeNew instance every timeLazy (on each request)
RequestOne instance per HTTP requestLazy
SessionOne instance per HTTP sessionLazy
ApplicationOne instance across multiple IoC containersEager

Scope 1: Singleton (Default)

Definition: Only one instance is created per IoC container. Every class or bean that depends on a singleton receives the same object.

This is the default scope. If you do not specify a scope, Spring treats the bean as singleton.

java
import org.springframework.stereotype.Component;

// All three declarations below result in the same singleton scope:

@Component  // default — singleton
public class UserRepository { }

@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)  // explicit enum
public class UserRepository { }

@Component
@Scope("singleton")  // explicit string
public class UserRepository { }

Demonstrating Singleton Behaviour

java
@Component
public class UserRepository {
    public UserRepository() {
        System.out.println("[UserRepository] Created — hashCode: " + this.hashCode());
    }
}

@RestController
public class ControllerA {
    private final UserRepository userRepository;

    @Autowired
    public ControllerA(UserRepository userRepository) {
        this.userRepository = userRepository;
        System.out.println("[ControllerA] userRepository hashCode: " + userRepository.hashCode());
    }
}

@RestController
public class ControllerB {
    private final UserRepository userRepository;

    @Autowired
    public ControllerB(UserRepository userRepository) {
        this.userRepository = userRepository;
        System.out.println("[ControllerB] userRepository hashCode: " + userRepository.hashCode());
    }
}

Expected startup output:

[UserRepository] Created — hashCode: 112358132
[ControllerA] userRepository hashCode: 112358132
[ControllerB] userRepository hashCode: 112358132

Both controllers receive the same UserRepository instance. After startup, subsequent API calls do not create any new objects. The singleton object is used for every request.

When to Use Singleton

Use singleton for stateless components: services, repositories, clients, caches. Most beans in a Spring application are singletons.


Scope 2: Prototype

Definition: A new instance is created every time the bean is requested from the container.

java
import org.springframework.context.annotation.Scope;

@Component
@Scope("prototype")
// or @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ReportBuilder {
    public ReportBuilder() {
        System.out.println("[ReportBuilder] New instance — hashCode: " + this.hashCode());
    }
}

Demonstrating Prototype Behaviour

java
@Component
@Scope("prototype")
public class UserSession {
    public UserSession() {
        System.out.println("[UserSession] Created — hashCode: " + this.hashCode());
    }
}

@Component
public class Student {
    // Student is singleton — its UserSession dependency is resolved once
    private final UserSession userSession;

    @Autowired
    public Student(UserSession userSession) {
        this.userSession = userSession;
        System.out.println("[Student] userSession hashCode: " + userSession.hashCode());
    }
}

@RestController
@Scope("prototype")
public class ApiController {
    private final UserSession userSession;
    private final Student student;

    @Autowired
    public ApiController(UserSession userSession, Student student) {
        this.userSession = userSession;
        this.student = student;
    }

    @GetMapping("/fetch")
    public String fetch() {
        return "controller userSession: " + userSession.hashCode()
             + ", student userSession: " + student.getUserSession().hashCode();
    }
}

What happens at startup:

  • ApiController is prototype → not created at startup
  • Student is singleton → eagerly created at startup
  • Student needs a UserSession (prototype) → one UserSession is created for Student

What happens on first API call to /fetch:

  • A new ApiController is created (prototype)
  • A new UserSession is created for ApiController (prototype)
  • Student reuses its own UserSession (singleton uses same prototype instance forever)

Important: When a singleton bean depends on a prototype bean, the singleton holds the same prototype instance forever. It does not get a new prototype on every method call. To get a new prototype instance on each call from within a singleton, you need to use ApplicationContext.getBean() or ObjectProvider<T>.

When to Use Prototype

Use prototype for stateful objects that should not be shared: user specific data structures, report builders, form objects. Avoid it for heavyweight beans that are expensive to create.


Scope 3: Request

Definition: A new bean instance is created for each HTTP request. Within a single request, the same instance is shared across all components that need it. When the request ends, the bean is destroyed.

java
import org.springframework.web.context.annotation.RequestScope;

@Component
@RequestScope
// equivalent to @Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class RequestContext {
    private String requestId;
    private String userId;

    public RequestContext() {
        System.out.println("[RequestContext] Created for new HTTP request");
    }

    // getters and setters
}

Demonstrating Request Scope

java
@Component
@RequestScope
public class UserContext {
    public UserContext() {
        System.out.println("[UserContext] Created — hashCode: " + this.hashCode());
    }
}

@RestController
public class OrderController {
    private final UserContext userContext;
    private final OrderProcessor orderProcessor;

    @Autowired
    public OrderController(UserContext userContext, OrderProcessor orderProcessor) {
        this.userContext = userContext;
        this.orderProcessor = orderProcessor;
    }

    @GetMapping("/order")
    public String placeOrder() {
        // Both this controller and OrderProcessor see the SAME UserContext instance
        // within this single HTTP request
        return "controller UserContext: " + userContext.hashCode()
             + ", processor UserContext: " + orderProcessor.getUserContext().hashCode();
    }
}

@Component
@Scope("prototype")
public class OrderProcessor {
    private final UserContext userContext;

    @Autowired
    public OrderProcessor(UserContext userContext) {
        this.userContext = userContext;
    }

    public UserContext getUserContext() { return userContext; }
}

First HTTP request: One UserContext created. Both OrderController and OrderProcessor share it. Second HTTP request: A brand new UserContext is created. The previous one is discarded.

The Proxy Mode Problem

Consider this scenario: a singleton bean depends on a request scoped bean.

java
@RestController  // Singleton — eagerly initialised at startup
public class DashboardController {

    @Autowired
    private UserContext userContext;  // Request scoped

    @GetMapping("/dashboard")
    public String show() {
        return userContext.getUserId();
    }
}

At application startup, Spring tries to create DashboardController. It looks for a UserContext bean to inject. But UserContext is request scoped — it can only exist within an active HTTP request. At startup, there is no HTTP request. The application fails.

Solution: ScopedProxyMode

java
@Component
@Scope(
    value = WebApplicationContext.SCOPE_REQUEST,
    proxyMode = ScopedProxyMode.TARGET_CLASS  // Create a proxy at startup
)
public class UserContext {
    private String userId;
    // ...
}

With proxyMode = ScopedProxyMode.TARGET_CLASS, Spring creates a proxy object at startup and injects that proxy into the singleton. The proxy looks like a real UserContext but delegates all actual calls to the real, request bound instance. When an HTTP request arrives and you call userContext.getUserId(), the proxy transparently resolves the actual request scoped bean and forwards the call.

Startup:
  DashboardController ← proxy(UserContext) [no real UserContext yet]

HTTP Request arrives:
  proxy(UserContext) → real UserContext for this request → getUserId()

HTTP Request ends:
  real UserContext destroyed
  proxy still alive, ready for next request

Scope 4: Session

Definition: One instance per HTTP session. Unlike request scope (one per request), session scope creates an instance when the session is first used and keeps it alive until the session expires or is explicitly invalidated.

java
import org.springframework.web.context.annotation.SessionScope;

@Component
@SessionScope
// equivalent to @Scope(value = WebApplicationContext.SCOPE_SESSION)
public class ShoppingCart {
    private List<String> items = new ArrayList<>();

    public void addItem(String item) { items.add(item); }
    public List<String> getItems() { return items; }

    public ShoppingCart() {
        System.out.println("[ShoppingCart] Created for new HTTP session");
    }
}

Demonstrating Session Scope

java
@RestController
public class CartController {

    private final ShoppingCart cart;

    @Autowired
    public CartController(ShoppingCart cart) {
        this.cart = cart;
    }

    @PostMapping("/cart/add")
    public String addItem(@RequestParam String item) {
        cart.addItem(item);
        return "Added: " + item;
    }

    @GetMapping("/cart")
    public List<String> viewCart() {
        return cart.getItems();
    }

    @PostMapping("/logout")
    public String logout(HttpServletRequest request) {
        request.getSession().invalidate(); // ends session, destroys ShoppingCart
        return "Logged out";
    }
}

Lifecycle:

  1. User hits /cart/add for the first time → HTTP session created → ShoppingCart bean created
  2. User calls /cart/add again → same session active → same ShoppingCart instance used (items persist)
  3. User calls /logout → session invalidated → ShoppingCart destroyed
  4. User calls /cart/add again → new session → new ShoppingCart created from scratch

A singleton bean depending on a session scoped bean has the same proxy problem as with request scope. Use proxyMode = ScopedProxyMode.TARGET_CLASS on the session scoped bean.


Scope 5: Application

Definition: One instance shared across multiple IoC containers within the same ServletContext. Functionally very similar to singleton (one object per application), but the distinction matters when multiple Spring contexts are running in the same JVM.

java
@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
public class GlobalConfig {
    private String appName = "MyApp";
    // getters, setters
}

In practice, most applications run a single IoC container. Application scope is rarely needed, but it is worth knowing for completeness.


Putting It All Together

java
// Singleton — one instance, created at startup
@Service
public class ProductService { }

// Prototype — new instance every time it is requested
@Component
@Scope("prototype")
public class CartItem { }

// Request — one per HTTP request, proxy needed when injected into singletons
@Component
@RequestScope
public class RequestLogger { }

// Session — one per user session, proxy needed when injected into singletons
@Component
@SessionScope
public class UserPreferences { }

Summary

ScopeInstancesCreated WhenDestroyed When
SingletonOne per IoC containerApp startupApp shutdown
PrototypeNew for each injection/lookupOn demandWhen GC collected
RequestOne per HTTP requestFirst use in requestRequest ends
SessionOne per HTTP sessionFirst use in sessionSession expires/invalidated
ApplicationOne per ServletContextApp startupApp shutdown

Interview Questions

Q1. What is the default scope of a Spring bean? Singleton. If you do not specify a scope, Spring creates exactly one instance of the bean per IoC container and reuses it everywhere.

Q2. What is the difference between singleton and prototype scope? Singleton creates one instance per IoC container, shared across all usages. Prototype creates a fresh instance every time the bean is requested from the container. Singletons are eagerly initialised; prototypes are lazily initialised.

Q3. What is the difference between request scope and session scope? Request scope creates a new bean for each HTTP request and destroys it when the request ends. Session scope creates a bean when an HTTP session begins and keeps it alive for the duration of the session (across multiple requests) until the session expires or is invalidated.

Q4. What is the proxy mode and why is it needed? When a shorter lived bean (request or session scoped) is injected into a longer lived bean (singleton), Spring cannot inject the real bean at startup because it does not exist yet. ScopedProxyMode.TARGET_CLASS tells Spring to inject a proxy instead. The proxy delegates method calls to the actual bean at runtime, which is created when the appropriate scope (request, session) becomes active.

Q5. If a singleton bean depends on a prototype bean, does it get a new prototype on every method call? No. The singleton holds the same prototype instance it received when it was first created. To get a new prototype instance on each method call from within a singleton, you need to use ObjectProvider<T> or look up the bean directly from ApplicationContext.getBean().

Q6. Why is prototype scope said to be lazily initialised? Prototype beans are not created at application startup. They are only created when something actually requests them — either because a dependency injection point triggers it, or because code calls applicationContext.getBean(). This contrasts with singleton beans, which are eagerly created at startup.

Q7. What is the application scope? How does it differ from singleton? Application scope creates one bean instance per ServletContext, which means one instance shared across multiple IoC containers if multiple exist in the same JVM. Singleton scope creates one instance per IoC container. In typical applications with one container, they behave identically.

Q8. How do you declare a prototype scoped bean? Annotate the class with @Scope("prototype") or @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) in addition to the stereotype annotation (e.g., @Component).