Skip to main content

Spring Bean Scopes

Introduction

In Spring Framework, the scope of a bean defines the lifecycle and visibility of that bean within the container. When you define a bean, you not only tell Spring how to create it but also how long it should live and how widely it should be shared. Understanding bean scopes is crucial for developing robust Spring applications because choosing the wrong scope can lead to unexpected behavior, memory leaks, or concurrency issues.

This guide will introduce you to the various bean scopes available in Spring, explain when to use each one, and provide practical examples to illustrate their behavior.

Core Bean Scopes in Spring

Spring Framework provides several bean scopes out of the box:

  1. Singleton - Default scope; one instance per container
  2. Prototype - New instance each time the bean is requested
  3. Request - One instance per HTTP request (web-aware)
  4. Session - One instance per HTTP session (web-aware)
  5. Application - One instance per ServletContext (web-aware)
  6. Websocket - One instance per WebSocket (web-aware)

Let's explore each of these in detail.

Singleton Scope

The singleton scope is Spring's default bean scope. When a bean is defined as a singleton, the Spring container creates exactly one instance of the object defined by that bean definition. This single instance is stored in a cache of singleton beans, and all subsequent requests and references for that bean return the cached object.

How to define a Singleton bean

java
@Component
// or explicitly:
@Scope("singleton")
public class UserService {
// ...
}

Or in XML configuration:

xml
<bean id="userService" class="com.example.UserService" scope="singleton" />

Example of Singleton behavior

Let's see the singleton scope in action:

java
@Component
public class CounterService {
private int count = 0;

public void increment() {
count++;
}

public int getCount() {
return count;
}
}

Now, let's use this in a Spring application:

java
@SpringBootApplication
public class SingletonScopeExample implements CommandLineRunner {

@Autowired
private ApplicationContext context;

public static void main(String[] args) {
SpringApplication.run(SingletonScopeExample.class, args);
}

@Override
public void run(String... args) {
CounterService counter1 = context.getBean(CounterService.class);
counter1.increment();
System.out.println("Counter1: " + counter1.getCount()); // Output: 1

CounterService counter2 = context.getBean(CounterService.class);
System.out.println("Counter2: " + counter2.getCount()); // Output: 1

counter2.increment();
System.out.println("After counter2 increment:");
System.out.println("Counter1: " + counter1.getCount()); // Output: 2
System.out.println("Counter2: " + counter2.getCount()); // Output: 2

System.out.println("Are beans the same instance? " + (counter1 == counter2)); // Output: true
}
}

In this example, both counter1 and counter2 refer to the same instance, demonstrating the singleton nature of the bean.

When to use Singleton scope

Use singleton scope when:

  • You need to share state across the application
  • The bean is stateless
  • The bean is expensive to create and can be safely shared
  • You want to cache data across the application

Prototype Scope

The prototype scope results in the creation of a new bean instance every time it is requested from the container. Unlike the singleton scope, Spring does not manage the complete lifecycle of a prototype bean - the container instantiates, configures, and assembles a prototype object and hands it to the client, without further record of that instance.

How to define a Prototype bean

java
@Component
@Scope("prototype")
public class ShoppingCart {
private List<String> items = new ArrayList<>();

public void addItem(String item) {
items.add(item);
}

public List<String> getItems() {
return items;
}
}

Or in XML:

xml
<bean id="shoppingCart" class="com.example.ShoppingCart" scope="prototype" />

Example of Prototype behavior

Let's see the prototype scope in action:

java
@SpringBootApplication
public class PrototypeScopeExample implements CommandLineRunner {

@Autowired
private ApplicationContext context;

public static void main(String[] args) {
SpringApplication.run(PrototypeScopeExample.class, args);
}

@Override
public void run(String... args) {
ShoppingCart cart1 = context.getBean(ShoppingCart.class);
cart1.addItem("Laptop");
System.out.println("Cart1 items: " + cart1.getItems()); // Output: [Laptop]

ShoppingCart cart2 = context.getBean(ShoppingCart.class);
System.out.println("Cart2 items: " + cart2.getItems()); // Output: [] (empty)

cart2.addItem("Phone");
System.out.println("After adding to cart2:");
System.out.println("Cart1 items: " + cart1.getItems()); // Output: [Laptop]
System.out.println("Cart2 items: " + cart2.getItems()); // Output: [Phone]

System.out.println("Are beans the same instance? " + (cart1 == cart2)); // Output: false
}
}

In this example, cart1 and cart2 are different instances, showing the prototype nature of the bean.

When to use Prototype scope

Use prototype scope when:

  • The bean is stateful
  • Each client needs its own instance
  • You need different configurations of the same bean
  • You want to avoid sharing state across different parts of the application

Web-Aware Scopes

Spring also provides several scopes that are only available in a web application context:

Request Scope

Request scope creates a bean instance for each HTTP request.

java
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
private final String id = UUID.randomUUID().toString();

public String getId() {
return id;
}
}

The proxyMode attribute is important for web scopes. It tells Spring to create a proxy that will be injected into other beans. The actual target object will be created when the request comes in.

Session Scope

Session scope creates a bean instance for each HTTP session.

java
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserPreferences {
private String theme = "light";

public String getTheme() {
return theme;
}

public void setTheme(String theme) {
this.theme = theme;
}
}

Application Scope

Application scope creates a bean instance for the lifecycle of a ServletContext.

java
@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class AppConfig {
private final Map<String, String> settings = new HashMap<>();

// Methods to update/retrieve settings
}

Custom Scopes

Spring also allows you to define your own bean scopes by implementing the org.springframework.beans.factory.config.Scope interface and registering it with the container.

Here's a simple example of a custom thread scope that creates a bean instance per thread:

java
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadScope =
ThreadLocal.withInitial(HashMap::new);

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = threadScope.get();
return scope.computeIfAbsent(name, k -> objectFactory.getObject());
}

@Override
public Object remove(String name) {
Map<String, Object> scope = threadScope.get();
return scope.remove(name);
}

@Override
public void registerDestructionCallback(String name, Runnable callback) {
// Register a callback to be executed when the thread ends
}

@Override
public Object resolveContextualObject(String key) {
return null;
}

@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}

To register this custom scope:

java
@Configuration
public class AppConfig implements ApplicationContextAware {

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ConfigurableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
beanFactory.registerScope("thread", new ThreadScope());
}
}

Common Issues with Bean Scopes

Singleton Beans Depending on Prototype Beans

One common issue occurs when a singleton bean depends on a prototype bean. Since the singleton is only created once, the prototype dependency will also only be injected once, defeating the purpose of the prototype scope.

To solve this problem, you can:

  1. Use method injection with the @Lookup annotation:
java
@Component
public abstract class UserManager {

@Lookup
public abstract ShoppingCart getShoppingCart();

public void processUserCart(String userId) {
ShoppingCart cart = getShoppingCart(); // Gets a new instance each time
// Process the cart...
}
}
  1. Inject the ApplicationContext and get the bean manually:
java
@Component
public class UserManager {

@Autowired
private ApplicationContext applicationContext;

public void processUserCart(String userId) {
ShoppingCart cart = applicationContext.getBean(ShoppingCart.class);
// Process the cart...
}
}
  1. Use scoped proxies (as seen in web scopes examples):
java
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
// Class implementation...
}

Real-World Application Examples

Singleton for Services

In a typical layered architecture, service beans are often stateless and can be safely shared across the application:

java
@Service
// Implicit singleton scope
public class ProductService {

@Autowired
private ProductRepository repository;

public List<Product> findAllProducts() {
return repository.findAll();
}

public Product findById(Long id) {
return repository.findById(id).orElse(null);
}
}

Prototype for User State

When you need to maintain state for individual operations:

java
@Component
@Scope("prototype")
public class ImportProcess {
private List<String> processedItems = new ArrayList<>();
private int errors = 0;

public void processItem(String item) {
try {
// Process the item...
processedItems.add(item);
} catch (Exception e) {
errors++;
}
}

public ImportSummary getSummary() {
return new ImportSummary(processedItems.size(), errors);
}
}

Request Scope for Web Tracking

Request scoped beans are useful for tracking information about the current request:

java
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestTracker {
private final String requestId = UUID.randomUUID().toString();
private final long startTime = System.currentTimeMillis();
private String userAgent;
private String clientIp;

public void record(HttpServletRequest request) {
this.userAgent = request.getHeader("User-Agent");
this.clientIp = request.getRemoteAddr();
}

public RequestLog createLog() {
long duration = System.currentTimeMillis() - startTime;
return new RequestLog(requestId, userAgent, clientIp, duration);
}
}

Session Scope for User Settings

Session scope is perfect for storing user preferences or state across requests:

java
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
private String userId;
private Set<String> permissions = new HashSet<>();
private Map<String, String> preferences = new HashMap<>();

public boolean isLoggedIn() {
return userId != null;
}

public void login(String userId, Set<String> permissions) {
this.userId = userId;
this.permissions.addAll(permissions);
}

public void logout() {
this.userId = null;
this.permissions.clear();
}

// Additional methods...
}

Summary

Bean scopes in Spring determine the lifecycle and sharing of bean instances. Here's a quick recap:

  1. Singleton - The default scope; one shared instance per container
  2. Prototype - A new instance is created each time the bean is requested
  3. Request - One instance per HTTP request (web applications only)
  4. Session - One instance per HTTP session (web applications only)
  5. Application - One instance per ServletContext (web applications only)
  6. WebSocket - One instance per WebSocket (web applications only)

Choosing the right scope is important:

  • Use singleton for stateless, shared services
  • Use prototype for stateful beans that need a fresh instance each time
  • Use web scopes for beans that need to be tied to web context objects

Remember that Spring only fully manages the lifecycle of singleton beans. For prototype beans, Spring creates the instance but does not manage its destruction.

Additional Resources and Exercises

Further Reading

Exercises

  1. Create a simple Spring Boot application that demonstrates the difference between singleton and prototype beans.

  2. Create a web application using Spring Boot that uses request-scoped beans to track performance metrics for each request.

  3. Implement a custom thread-scoped bean and use it to store context information that's specific to each thread in your application.

  4. Create a session-scoped shopping cart in a Spring MVC web application.

  5. Experiment with the use of @Lookup annotation to solve the singleton-depends-on-prototype issue mentioned in the common issues section.

By mastering bean scopes, you'll be able to design more robust and efficient Spring applications that make appropriate use of resources and maintain state correctly.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)