Custom Bean Scope in Spring

Last Updated : 4 Apr, 2026

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.

Java
<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.

ou
Project-Structure

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.

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.

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.

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.

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.
ou
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.
Comment

Explore