Spring Boot
Spring Framework
Component Instantiation
Java Development
Dependency Injection

Create a new instance of component in Spring Boot/Framework

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Spring manages beans as singletons by default — every @Autowired injection receives the same instance. When you need a new instance of a component each time, use @Scope("prototype") on the bean, ObjectFactory<T> or ObjectProvider<T> for lazy retrieval, ApplicationContext.getBean() for manual lookup, or @Lookup for method injection. Each approach has different trade-offs for testability, thread safety, and coupling to the Spring framework.

Default Singleton Scope

java
1@Component
2public class NotificationService {
3    private int count = 0;
4
5    public void send(String message) {
6        count++;
7        System.out.println("Notification #" + count + ": " + message);
8    }
9}
10
11@RestController
12public class UserController {
13    @Autowired
14    private NotificationService notificationService; // Same instance everywhere
15
16    @GetMapping("/notify")
17    public String notify() {
18        notificationService.send("Hello");
19        return "sent";
20    }
21}

By default, Spring creates one instance of NotificationService shared across all injection points. The count field accumulates across all requests.

Method 1: Prototype Scope with ObjectProvider

java
1@Component
2@Scope("prototype")
3public class ReportGenerator {
4    private final List<String> data = new ArrayList<>();
5
6    public void addRow(String row) {
7        data.add(row);
8    }
9
10    public String generate() {
11        return String.join("\n", data);
12    }
13}
14
15@Service
16public class ReportService {
17    private final ObjectProvider<ReportGenerator> reportProvider;
18
19    public ReportService(ObjectProvider<ReportGenerator> reportProvider) {
20        this.reportProvider = reportProvider;
21    }
22
23    public String createReport(List<String> rows) {
24        // New instance each time getObject() is called
25        ReportGenerator generator = reportProvider.getObject();
26        rows.forEach(generator::addRow);
27        return generator.generate();
28    }
29}

@Scope("prototype") tells Spring to create a new instance for each request. ObjectProvider<T> lazily retrieves a new instance when getObject() is called, avoiding the common pitfall of injecting a prototype into a singleton (which would give you the same instance).

Method 2: ApplicationContext.getBean()

java
1@Service
2public class TaskService {
3    @Autowired
4    private ApplicationContext context;
5
6    public void processTask(String taskId) {
7        // New instance each call
8        TaskProcessor processor = context.getBean(TaskProcessor.class);
9        processor.execute(taskId);
10    }
11}
12
13@Component
14@Scope("prototype")
15public class TaskProcessor {
16    public void execute(String taskId) {
17        System.out.println("Processing: " + taskId + " in " + this);
18    }
19}

ApplicationContext.getBean() creates a new instance for prototype-scoped beans. This works but couples your code to the Spring API, making unit testing harder.

Method 3: @Lookup Method Injection

java
1@Service
2public abstract class OrderService {
3
4    // Spring overrides this method to return a new prototype instance
5    @Lookup
6    protected abstract OrderProcessor createProcessor();
7
8    public void processOrder(String orderId) {
9        OrderProcessor processor = createProcessor(); // New instance
10        processor.process(orderId);
11    }
12}
13
14@Component
15@Scope("prototype")
16public class OrderProcessor {
17    public void process(String orderId) {
18        System.out.println("Processing order: " + orderId + " by " + this);
19    }
20}

@Lookup tells Spring to override the abstract method with a bean-lookup implementation at runtime via CGLIB subclassing. The service class must be non-final (or abstract).

Method 4: @Bean Factory Method

java
1@Configuration
2public class AppConfig {
3
4    @Bean
5    @Scope("prototype")
6    public EmailSender emailSender() {
7        return new EmailSender();
8    }
9
10    // Factory method — returns a new instance each call
11    @Bean
12    public Supplier<EmailSender> emailSenderFactory() {
13        return this::emailSender;
14    }
15}
16
17@Service
18public class NotificationService {
19    private final Supplier<EmailSender> emailSenderFactory;
20
21    public NotificationService(Supplier<EmailSender> emailSenderFactory) {
22        this.emailSenderFactory = emailSenderFactory;
23    }
24
25    public void notify(String to, String body) {
26        EmailSender sender = emailSenderFactory.get(); // New instance
27        sender.send(to, body);
28    }
29}

A Supplier<T> factory bean provides a clean, framework-agnostic way to create new instances. The calling code has no Spring dependency.

Request Scope (Web Applications)

java
1@Component
2@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
3public class RequestContext {
4    private String userId;
5    private Instant startTime = Instant.now();
6
7    public void setUserId(String userId) { this.userId = userId; }
8    public String getUserId() { return userId; }
9    public Duration getElapsed() { return Duration.between(startTime, Instant.now()); }
10}
11
12@RestController
13public class ApiController {
14    @Autowired
15    private RequestContext requestContext; // New instance per HTTP request
16
17    @GetMapping("/api/data")
18    public String getData() {
19        requestContext.setUserId("user-123");
20        return "Elapsed: " + requestContext.getElapsed();
21    }
22}

SCOPE_REQUEST creates a new instance per HTTP request. proxyMode = TARGET_CLASS generates a CGLIB proxy so the singleton controller can hold a reference that delegates to the correct request-scoped instance.

Common Pitfalls

  • Injecting prototype into singleton directly: @Autowired private MyPrototype bean; in a singleton gives you one instance forever. The prototype scope only creates a new instance when the container is asked — use ObjectProvider, @Lookup, or a factory.
  • Forgetting @Scope("prototype") on the class: Without the scope annotation, getBean() and ObjectProvider still return the same singleton instance. Both the retrieval mechanism and the scope annotation are required.
  • CGLIB proxy requirements: @Lookup requires the class to be non-final and the method to be non-private. ScopedProxyMode.TARGET_CLASS requires CGLIB on the classpath (included in Spring Boot by default).
  • Prototype beans not destroyed by Spring: Spring does not manage the lifecycle of prototype beans after creation — @PreDestroy methods are never called. You must clean up resources manually.
  • Thread safety assumptions: Creating new instances per request is safer for mutable state, but adds garbage collection overhead. For stateless services, singleton scope is more efficient.

Summary

  • @Scope("prototype") — marks a bean for new-instance-per-request creation
  • ObjectProvider<T>.getObject() — cleanest way to get prototype instances (recommended)
  • @Lookup — method injection via abstract method, Spring generates the implementation
  • ApplicationContext.getBean() — works but couples code to Spring API
  • Supplier<T> factory bean — framework-agnostic approach for testability
  • @Scope(SCOPE_REQUEST) with proxyMode — new instance per HTTP request in web apps

Course illustration
Course illustration

All Rights Reserved.