In the Spring Framework, bean scopes define the lifecycle and visibility of beans within the application context. Spring provides several built-in scopes such as singleton, prototype, request, session, and application. However, some applications require more control over bean lifecycle, which can be achieved using custom bean scopes.
- Spring allows developers to create custom bean scopes when built-in scopes do not meet application needs.
- Custom scopes help control bean creation and destruction based on specific business logic.
- They provide greater flexibility in managing bean lifecycle in complex applications.
When to Use Custom Scopes
- When a bean needs to live beyond a single HTTP request but less than a singleton.
- When managing resources per thread, user, or tenant.
- When the built-in scopes do not provide the required lifecycle behavior.
Steps to Creating a Custom Thread-Local Scope
Step 1: Create a Maven Project
1. Open IntelliJ / Eclipse / STS.
2. Select File -> New -> Maven Project.
3. Choose Create a simple project (skip archetype selection).
4. Enter the following details:
- Group Id: com.example
- Artifact Id: custom-scope-demo
- Packaging: jar
- Java Version: 17 or later
Click Finish to create the project.
Step 2: Add Spring Dependency
Open the pom.xml file and add the Spring dependency.
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.6</version>
</dependency>
</dependencies>
Then update the Maven project so the dependencies are downloaded.
Step 3: Create Project Package Structure
Create the following package structure inside src/main/java.

Step 4: Implement the Custom Scope
Spring provides the interface:
org.springframework.beans.factory.config.Scope
We need to implement this interface to define the behavior of our custom scope.
Create the class ThreadLocalScope.java.
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 destruction logic
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
Explanation:
- Creates a ThreadLocal map to store bean instances.
- Each thread maintains its own copy of the bean.
- Beans are shared only within the same thread.
Step 5: Register the Custom Scope
Now we must register the custom scope in the Spring configuration.
Create AppConfig.java.
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();
}
}
Here we define a new scope named thread-local and register it with Spring.
Step 6: Create the Bean Class
MyBean.java.
package com.example.service;
public class MyBean {
public MyBean() {
System.out.println("MyBean instance created");
}
}
Step 7: Create Main Class to Test Custom Scope
CustomScopeDemo.java.
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();
}
}
Step 8: Run the Application
- Open the class CustomScopeDemo.java.
- Right-click on the file.
- Click Run As -> Java Application.
- The program will start and execute the threads.
- The console will display the output.

Advantages of Custom Bean Scopes
- Fine-grained control over bean lifecycle.
- Enables per-thread, per-user, or per-conversation bean management.
- Reduces resource usage by limiting bean lifespan to specific contexts.
- Improves modularity by isolating beans according to business requirements.