🍃 Section 5 · Question #11

What scopes exist in Spring

In the Spring Framework, bean scopes are partitioned into two architectural categories: Universal Scopes (available in any Java application, including CLI, batch, and desktop) a...


🟢 Junior Level

1. Classification of Scopes in Spring

In the Spring Framework, bean scopes are partitioned into two architectural categories: Universal Scopes (available in any Java application, including CLI, batch, and desktop) and Web-Aware Scopes (available only within web-aware contexts such as Spring MVC and Spring Boot Web).

Summary Table of All Standard Scopes

Scope Name Environment Lifecycle Boundary & Instance Cardinality
singleton (Default) Universal Exactly 1 instance per ApplicationContext. Exists until the container closes.
prototype Universal A new instance is created on every injection or getBean() invocation.
request Web / Servlet 1 instance per individual HTTP Request. Destroyed when the request completes.
session Web / Servlet 1 instance per HTTP Session. Lives as long as the user session remains active.
application Web / Servlet 1 instance per ServletContext. Shared across all servlets in the container.
websocket Web / WebSocket 1 instance per WebSocket session lifecycle.

2. Declaration Syntax in Modern Spring Boot

In modern Spring Boot applications, convenient composed stereotype annotations replace verbose XML or string-based scope declarations:

package com.example.scopes;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.ApplicationScope;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.context.annotation.SessionScope;

// 1. Singleton Scope (Default - annotation optional)
@Component
public class CatalogService { }

// 2. Prototype Scope
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class HeavyCalculationTask { }

// 3. Request Scope (Alias for @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS))
@Component
@RequestScope
public class RequestAuditContext { }

// 4. Session Scope (Alias for @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS))
@Component
@SessionScope
public class UserShoppingCart { }

// 5. Application Scope (Alias for @Scope(value = "application", proxyMode = ScopedProxyMode.TARGET_CLASS))
@Component
@ApplicationScope
public class ServletContextMetrics { }

🟡 Middle Level

1. Internal Storage of Web Scopes

Web-aware scopes rely on Spring’s RequestContextHolder, which stores the current request’s RequestAttributes inside a ThreadLocal:

  1. request Scope:
    • The bean instance is bound to the current servlet request: HttpServletRequest.setAttribute(name, bean).
    • When the HTTP thread finishes executing, the servlet container cleans up request attributes, and Spring triggers registered destruction callbacks.
  2. session Scope:
    • The bean instance is stored in the standard servlet session: HttpSession.setAttribute(name, bean).
    • Enables persisting user-specific state (shopping carts, user authentication tokens, locale preferences) across multiple HTTP requests from the same client.
  3. application Scope vs singleton:
    • singleton: Bound to a single Spring ApplicationContext.
    • application: Bound to the web container’s ServletContext.
    • Architectural Difference: In enterprise setups where multiple DispatcherServlet instances run inside a single web container, each servlet possesses its own child ApplicationContext with separate singleton beans, but they all share the exact same @ApplicationScope bean.

2. Scoped Proxy Modes: ScopedProxyMode

When injecting a short-lived bean (request, session) into a long-lived singleton bean, you must configure proxyMode:

ScopedProxyMode Options:
├── DEFAULT / NO:
│   └── No proxy is generated. Injecting into a singleton causes Scoped Mismatch (freezes the instance).
├── TARGET_CLASS (Default for @RequestScope / @SessionScope):
│   └── Generates a CGLIB dynamic subclass proxy. Works on any concrete class.
└── INTERFACES:
    └── Generates a JDK Dynamic Proxy. Requires the target bean to implement an interface.
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserSessionContext userSessionContext() {
    return new UserSessionContext();
}

🔴 Senior Level

1. Spring Cloud @RefreshScope

In cloud-native microservices, configuration properties change dynamically (via Spring Cloud Config Server, Consul, or Vault) without requiring a JVM or container restart:

@Component
@RefreshScope
@ConfigurationProperties(prefix = "payment.gateway")
public class PaymentGatewayProperties {
    private String apiUrl;
    private int timeoutMs;
    // Getters and setters
}

Under the Hood Mechanics of @RefreshScope:

  1. Spring Cloud registers a specialized RefreshScope implementation of org.springframework.beans.factory.config.Scope.
  2. The bean is wrapped in a dynamic CGLIB proxy backed by a RefreshScope interceptor.
  3. Bean instances are stored in an internal cache (BeanLifecycleDecoratorCache).
  4. When a /actuator/refresh HTTP endpoint is invoked (or a Kafka/RabbitMQ RefreshEvent is received), the RefreshScope clears its internal instance cache.
  5. On the very next method call through the CGLIB proxy, the interceptor detects a cache miss, re-reads updated properties from the Spring Environment, and instantiates a brand-new target instance!

2. Clustered Microservice Hazards: Native @SessionScope

In modern containerized deployments (Kubernetes pods behind load balancers):

HTTP Request 1 (Login)    ──► [Load Balancer] ──► [Pod 1] (Session stored in Pod 1 heap)
HTTP Request 2 (Checkout) ──► [Load Balancer] ──► [Pod 2] (Session NOT FOUND ──► 401 Unauthorized!)

Architectural Vulnerability:

Default @SessionScope stores state inside the local JVM heap of a single Tomcat instance. In multi-pod deployments without sticky sessions, subsequent requests fail.

Production Solution: Spring Session + Redis

Integrate Spring Session backed by a shared Redis cluster (@EnableRedisHttpSession):

  • The HTTP session state is serialized and stored in Redis.
  • Every pod in the Kubernetes cluster retrieves and mutates the exact same shared session, rendering @SessionScope horizontally scalable.

3. The SimpleThreadScope Hazard in Thread Pools

Spring provides an out-of-the-box SimpleThreadScope bound to ThreadLocal, but intentionally does not enable it by default:

@Configuration
public class ThreadScopeConfig {
    @Bean
    public static CustomScopeConfigurer customScopeConfigurer() {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("thread", new SimpleThreadScope());
        return configurer;
    }
}

[!WARNING] Why SimpleThreadScope is Dangerous in Enterprise Web Applications:

  1. Thread Pool Contamination: Application servers (Tomcat, Undertow) and async executors (ThreadPoolTaskExecutor) reuse threads. When a thread finishes processing a request and returns to the pool, its ThreadLocal is not automatically cleared. The next request handled by that worker thread inadvertently accesses dirty state from a previous user!
  2. No Destruction Callbacks: SimpleThreadScope does not support destruction callbacks; @PreDestroy and DisposableBean.destroy() are never invoked, causing native memory leaks.

4 Tricky Questions

1. What is the fundamental difference between singleton scope and application scope?

Answer: A singleton bean exists as a single instance per Spring ApplicationContext.

An application scoped bean (@ApplicationScope) exists as a single instance per ServletContext of the Java web container.

In enterprise deployments where multiple DispatcherServlet contexts reside within a single web application (e.g., legacy multi-module WAR deployments), each servlet owns its own child ApplicationContext with its own isolated singleton beans. However, beans marked with @ApplicationScope are stored directly in ServletContext.setAttribute() and are shared globally across all servlet contexts in that web application.


2. How does the composed annotation @RequestScope work under the hood, and why is ScopedProxyMode.TARGET_CLASS enabled by default?

Answer: @RequestScope is a composed meta-annotation defined as:

@Scope(WebApplicationContext.SCOPE_REQUEST)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestScope {
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

By defaulting to ScopedProxyMode.TARGET_CLASS, Spring automatically wraps the bean in a CGLIB dynamic proxy. This allows request-scoped beans to be injected safely into singleton controllers and services at startup. When client code invokes a method on the proxy, the proxy intercepts the call and dynamically looks up the active bean instance from RequestContextHolder (ThreadLocal) for the current HTTP worker thread.


3. How does Spring Cloud’s @RefreshScope dynamically update configuration without restarting the JVM?

Answer: Spring Cloud wraps @RefreshScope beans in a dynamic CGLIB proxy with a custom interceptor. Bean instances are stored in an internal cache managed by the RefreshScope class.

When an /actuator/refresh request is triggered:

  1. Spring Cloud publishes a RefreshEvent and reloads the PropertySource hierarchy.
  2. The RefreshScope invalidates and clears its internal cache of target bean instances.
  3. The next time application code invokes any method on the CGLIB proxy, the proxy detects that the cached target instance is missing.
  4. It calls createBeanInstance() to construct a brand-new bean instance, binding the updated environment configuration properties, and caches it.

4. Why is using Spring’s built-in SimpleThreadScope considered hazardous in production web applications?

Answer: SimpleThreadScope binds bean instances to the current thread using ThreadLocal.

In modern web applications, incoming requests are handled by thread pools (e.g., Tomcat worker threads). When a thread finishes servicing a request and returns to the pool, SimpleThreadScope does not automatically remove the ThreadLocal entries. When that thread is later assigned to process a request from a different user, it reuses the dirty, leftover bean instance from the previous request, causing data corruption, security breaches, and memory leaks. Furthermore, SimpleThreadScope does not support destruction callbacks (@PreDestroy is never called).


🎯 Interview Cheat Sheet

30-Second Elevator Pitch

“Spring provides 6 standard bean scopes:

  • Universal: singleton (1 per ApplicationContext, default) and prototype (new instance per request).
  • Web-Aware: request (1 per HTTP request), session (1 per HTTP session), application (1 per ServletContext), and websocket (1 per WebSocket connection).

In modern Spring Boot, composed annotations (@RequestScope, @SessionScope, @ApplicationScope) automatically configure ScopedProxyMode.TARGET_CLASS to allow safe injection into singletons via CGLIB dynamic proxies. In distributed microservice environments, @RefreshScope enables hot-reloading of configuration, and Spring Session + Redis replaces node-local in-memory session scope.”


Red Flags & Pitfalls (DO NOT Say)

  • ❌ “Web scopes can be used in standard command-line Java applications.” (Web scopes require a web-aware WebApplicationContext and RequestContextHolder).
  • ❌ “In clustered microservices, @SessionScope beans synchronize across nodes automatically.” (Standard @SessionScope is node-local; you must use Spring Session with Redis).
  • ❌ “The @RefreshScope annotation restarts the entire Spring Boot ApplicationContext.” (It only clears the proxy’s instance cache, re-instantiating only the affected beans).
  • ❌ “Singleton scope and application scope are the exact same thing.” (Singleton is 1 per ApplicationContext; application is 1 per ServletContext).

Complete Scope Comparison Matrix

Scope Feature Comparison:
┌─────────────┬─────────────┬────────────────────────────────┬───────────────────────────┐
│ Scope       │ Environment │ Storage Mechanism              │ Scoped Proxy Required     │
│             │             │                                │ in Singleton?             │
├─────────────┼─────────────┼────────────────────────────────┼───────────────────────────┤
│ singleton   │ Universal   │ DefaultSingletonBeanRegistry   │ No                        │
│ prototype   │ Universal   │ Not cached (Created on demand) │ Use ObjectProvider<T>     │
│ request     │ Web         │ HttpServletRequest attributes  │ Yes (TARGET_CLASS)        │
│ session     │ Web         │ HttpSession / Redis Session    │ Yes (TARGET_CLASS)        │
│ application │ Web         │ ServletContext attributes      │ Yes (TARGET_CLASS)        │
│ websocket   │ Web         │ WebSocketSession attributes    │ Yes (TARGET_CLASS)        │
│ refresh     │ Cloud       │ RefreshScope internal cache    │ Auto-proxied by framework │
└─────────────┴─────────────┴────────────────────────────────┴───────────────────────────┘