Question 10 · Section 5

What is Bean Scope?

Do NOT store mutable state in Singleton beans (e.g., user cache in a service field). This will lead to race conditions and memory leaks. Instead:

Language versions: English Russian Ukrainian

Junior Level

Scope defines how many instances of a bean Spring creates.

Main scopes:

Scope How many instances
Singleton One for the entire application (default)
Prototype New on each request
@Service  // Singleton by default
public class UserService { }

@Scope("prototype")
@Service
public class ReportGenerator { }

Example:

// Singleton: always the same
UserService s1 = context.getBean(UserService.class);
UserService s2 = context.getBean(UserService.class);
s1 == s2  // -> true

// Prototype: new each time
ReportGenerator r1 = context.getBean(ReportGenerator.class);
ReportGenerator r2 = context.getBean(ReportGenerator.class);
r1 == r2  // -> false

Middle Level

Singleton

// By default!
@Service
public class UserService { }

// One instance per ApplicationContext
// All threads use the same object
// -> Must be Stateless!

Prototype

@Scope("prototype")
@Service
public class ReportGenerator { }

// New instance on EVERY getBean()
// Spring creates it but does NOT manage destruction!
// -> @PreDestroy is NOT called

Web Scopes

@Scope("request")     // One instance per HTTP request
@Scope("session")     // One instance per HTTP session
@Scope("application") // One per ServletContext

Prototype in Singleton Problem

// Prototype created ONCE!
@Service
public class SingletonService {
    @Autowired
    private PrototypeBean prototype;  // Stuck with one instance!
}

// ObjectProvider
@Service
public class SingletonService {
    @Autowired
    private ObjectProvider<PrototypeBean> provider;

    public void process() {
        PrototypeBean bean = provider.getObject();  // New each time!
    }
}

Senior Level

When NOT to Use Singleton with Mutable State

Do NOT store mutable state in Singleton beans (e.g., user cache in a service field). This will lead to race conditions and memory leaks. Instead:

  • Extract state to external storage (Redis, DB)
  • Use Request scope for request data
  • Use Prototype if you need a new instance

Scoped Proxy

// Solution to Prototype/Request in Singleton problem
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences preferences() {
    return new UserPreferences();
}

// Spring injects a proxy into Singleton
// Proxy accesses RequestContextHolder (ThreadLocal) on each call, which stores the current HTTP request attributes. Thus, even a singleton bean injecting a request-scoped bean gets the current object for each request.

Under the Hood: Where Beans Are Stored

Singleton -> DefaultSingletonBeanRegistry (ConcurrentHashMap)
Request -> RequestContextHolder (ThreadLocal) - ScopedProxyMode.TARGET_CLASS creates a CGLIB proxy that accesses the current scope (request/session) on each call. RequestContextHolder stores current HTTP request data in ThreadLocal.
Session -> SessionAttributes
Prototype -> Created, not stored

Custom Scope

// Your own scope!
public class ThreadScope implements Scope {
    private final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();

    public Object get(String name, ObjectFactory<?> factory) {
        return threadLocal.get().computeIfAbsent(name, k -> factory.getObject());
    }
}

// Registration
context.getBeanFactory().registerScope("thread", new ThreadScope());

Production Experience

Real scenario: Request bean in Singleton

// Singleton received Request bean at startup
// -> All requests use the first user's data!

// ScopedProxyMode.TARGET_CLASS
// -> Proxy -> RequestContextHolder -> actual bean

Best Practices

  1. Singleton - by default (Stateless!)
  2. Prototype - rarely. If you just need a new object without Spring dependencies - use new. But if the object requires dependency injection - Prototype is justified.
  3. Request/Session - for web data
  4. Scoped Proxy - for injecting narrow scopes into wider ones
  5. ObjectProvider - alternative to Scoped Proxy. ObjectProvider vs ScopedProxy: ObjectProvider - explicit getObject() call in code (more readable). ScopedProxy - proxy is transparently substituted (less obvious but more convenient). ObjectProvider is preferred for explicit control.
  6. Avoid state in Singleton

Summary for Senior

  • Singleton = one per application, Stateless
  • Prototype = new each time, @PreDestroy does NOT work
  • Request/Session = web scope
  • Scoped Proxy = proxy -> RequestContextHolder
  • ObjectProvider = explicit bean request
  • Custom Scope = your own lifecycle

Interview Cheat Sheet

Must know:

  • Singleton (default) - one instance per ApplicationContext, must be Stateless
  • Prototype - new instance on each request, @PreDestroy is NOT called, Spring does not manage destruction
  • Web scopes: request (per HTTP request), session (per HTTP session), application (per ServletContext)
  • Prototype in Singleton problem: prototype is created ONCE when injected - use ObjectProvider.getObject()
  • Scoped Proxy (ScopedProxyMode.TARGET_CLASS) - CGLIB proxy accesses RequestContextHolder (ThreadLocal) on each call
  • Avoid mutable state in Singleton - race conditions and memory leaks
  • Custom Scope - can create your own (e.g., ThreadScope via ThreadLocal)
  • Singleton stores beans in DefaultSingletonBeanRegistry (ConcurrentHashMap), Request - in ThreadLocal

Common follow-up questions:

  • How to inject Prototype into Singleton? ObjectProvider<PrototypeBean>.getObject() - new each time, or Scoped Proxy.
  • What is a Scoped Proxy? A proxy that accesses the actual scope (request/session) via RequestContextHolder on each call.
  • Why must Singleton be Stateless? One object is used by all threads - mutable state = race condition.
  • When to use Prototype instead of new? When the object requires dependency injection from Spring.

Red flags (DO NOT say):

  • “Singleton can store mutable state” (race conditions and memory leaks)
  • “Prototype beans are destroyed automatically” (@PreDestroy is NOT called, clean manually)
  • “Request/Session scope works without Scoped Proxy in Singleton” (will be one instance for all requests!)
  • “ObjectProvider and Scoped Proxy are the same thing” (ObjectProvider - explicit getObject(), ScopedProxy - transparent substitution)

Related topics:

  • [[06. What is Bean Lifecycle]]
  • [[07. What are the Bean Lifecycle stages]]
  • [[09. What do PostConstruct and PreDestroy methods do]]
  • [[04. What is a Bean in Spring]]
  • [[01. What is Dependency Injection]]