Custom Bean Scope in Spring
1. Introduction
In Spring, bean scope defines:
Lifecycle of a bean
Visibility of a bean
Spring provides built-in scopes:
Singleton
Prototype
Request
Session
Application
But sometimes these are not enough. In such cases, we create custom bean scopes.
2. Why Custom Bean Scope
Custom scopes are useful when:
You need custom lifecycle control
Built-in scopes do not fit your requirement
You want to manage beans based on:
Thread
User
Tenant
3. When to Use Custom Scope
Use custom scope when:
Bean should live longer than request but not singleton
You want per-thread bean instance
You want custom lifecycle behavior
4. Steps to Create Custom Scope
Step 1: Add Dependency
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.6</version>
</dependency>Step 2: Implement Scope Interface
Spring provides:
org.springframework.beans.factory.config.ScopeCreate custom class:
package com.example.scope;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ThreadLocalScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadLocal =
ThreadLocal.withInitial(ConcurrentHashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scopedObjects = threadLocal.get();
return scopedObjects.computeIfAbsent(name,
k -> objectFactory.getObject());
}
@Override
public Object remove(String name) {
return threadLocal.get().remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// optional
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}Explanation
Uses ThreadLocal to store bean instances
Each thread gets its own bean
Beans are not shared across threads
Step 3: Register Custom Scope
package com.example.config;
import com.example.scope.ThreadLocalScope;
import com.example.service.MyBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.config.CustomScopeConfigurer;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class AppConfig {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer =
new CustomScopeConfigurer();
Map<String, Object> scopes = new HashMap<>();
scopes.put("thread-local", new ThreadLocalScope());
configurer.setScopes(scopes);
return configurer;
}
@Bean
@org.springframework.context.annotation.Scope("thread-local")
public MyBean myBean() {
return new MyBean();
}
}Step 4: Create Bean Class
package com.example.service;
public class MyBean {
public MyBean() {
System.out.println("MyBean instance created");
}
}Step 5: Test Custom Scope
package com.example;
import com.example.config.AppConfig;
import com.example.service.MyBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class CustomScopeDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
Runnable task = () -> {
MyBean bean1 = context.getBean(MyBean.class);
MyBean bean2 = context.getBean(MyBean.class);
System.out.println(Thread.currentThread().getName()
+ " : " + (bean1 == bean2));
};
Thread t1 = new Thread(task, "Thread-1");
Thread t2 = new Thread(task, "Thread-2");
t1.start();
t2.start();
}
}5. Output Understanding
Same thread → same bean instance → true
Different thread → different bean instance → false
6. Advantages of Custom Scope
Full control over bean lifecycle
Supports thread-based or user-based logic
Efficient resource usage
Better modular design
7. Conclusion
Custom bean scope is used in advanced scenarios
ThreadLocal scope is a common example
Helps manage beans beyond default Spring scopes