Spring Boot
Spring Framework
Component Instantiation
Java Development
Dependency Injection

Create a new instance of component in Spring Boot/Framework

Object-Oriented Design practice on Codemia

Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.

Practice OOD

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

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Object-Oriented Design practice on Codemia

Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.

Practice OOD