Understanding Bean Scopes in Spring Boot is essential for controlling how and when objects are created inside the Spring IoC container.
A Bean Scope defines:
import org.springframework.stereotype.Component;
@Component
public class EmailService {
public EmailService() {
System.out.println("EmailService instance created");
}
}
@Autowired private EmailService emailService;
โ Only one object is created and reused everywhere.
Spring Container
|
|----> [ EmailService ] (Single Instance)
| โ
| โ
Controller1 Controller2
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class PaymentService {
public PaymentService() {
System.out.println("PaymentService instance created");
}
}
@Autowired private PaymentService paymentService;
Every injection or manual getBean() call โ creates a new object
Request 1 โ [ PaymentService Instance 1 ] Request 2 โ [ PaymentService Instance 2 ] Request 3 โ [ PaymentService Instance 3 ]
When injecting prototype into singleton:
@Component
public class OrderService {
@Autowired
private PaymentService paymentService;
}
โ Only one prototype instance is injected at startup.
To get new instance every time, use:
ObjectProviderApplicationContext@LookupExample:
@Autowired
private ObjectProvider<PaymentService> provider;
public void process() {
PaymentService service = provider.getObject();
}
These require Spring Web dependency.
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class RequestBean {
}
HTTP Request 1 โ [ RequestBean Instance 1 ] HTTP Request 2 โ [ RequestBean Instance 2 ]
@Scope(WebApplicationContext.SCOPE_SESSION)
@Component
public class SessionBean {
}
User A Session โ [ SessionBean A ] User B Session โ [ SessionBean B ]
@Scope(WebApplicationContext.SCOPE_APPLICATION)
@Component
public class ApplicationBean {
}
| Scope | Instances Created | Shared Across | Typical Use Case |
|---|---|---|---|
| Singleton | 1 per container | Entire app | Services, Repos |
| Prototype | Every request | Not shared | Stateful objects |
| Request | Per HTTP request | Per request | Request data |
| Session | Per session | Per user | User session data |
| Application | Per web app | Entire app | Global web data |
Spring IoC Container
|
-----------------------------
| | |
Singleton Prototype Web Scopes
| | |
Created at Created on Created per
startup demand HTTP lifecycle
| Component | Recommended Scope |
|---|---|
| ProductService | Singleton |
| Cart (per user) | Session |
| PaymentTransaction | Prototype |
| RequestLogger | Request |
ObjectProvider when mixing scopesBean scopes control:
Understanding scopes helps you:
If you would like this exported, say:
Download as PDF / DOCX / PPT / Markdown / TXT ๐
ObjectProvider allows you to lazily fetch a new bean instance when required.
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final ObjectProvider<PaymentService> provider;
public OrderService(ObjectProvider<PaymentService> provider) {
this.provider = provider;
}
public void processOrder() {
PaymentService paymentService = provider.getObject();
paymentService.toString();
}
}
โ Each call to getObject() returns a new Prototype instance.
@Lookup lets Spring override a method to return a new Prototype bean each time.
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Component;
@Component
public abstract class ReportService {
public void generate() {
PrototypeBean bean = createPrototypeBean();
bean.toString();
}
@Lookup
protected abstract PrototypeBean createPrototypeBean();
}
โ Spring dynamically generates implementation at runtime.
@Lazy delays bean initialization until it is actually needed.
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
@Lazy
public class HeavyService {
public HeavyService() {
System.out.println("HeavyService initialized");
}
}
โ Bean is NOT created at application startup.
โ It is created only when first requested.
@Autowired @Lazy private HeavyService heavyService;
โ Useful for performance optimization and breaking circular dependencies.
Both ObjectProvider and @Lookup solve the same problem:
Getting a fresh Prototype bean inside a Singleton bean
| Feature | ObjectProvider | @Lookup |
|---|---|---|
| Type | Programmatic (explicit call) | Declarative (method override) |
| Flexibility | Very High | Limited |
| Optional Beans | Supported | Not Supported |
| Stream Support | Yes | No |
| Testing Friendly | Easier to mock | Harder |
| Recommended in modern apps | Yes | Rarely |
@Service
public class OrderService {
private final ObjectProvider<PaymentService> provider;
public OrderService(ObjectProvider<PaymentService> provider) {
this.provider = provider;
}
public void process() {
PaymentService service = provider.getObject();
service.processPayment();
}
}
โ Clear and explicit bean retrieval
โ Easier debugging
PaymentService service = provider.getIfAvailable();
โ No exception if bean does not exist
PaymentService service =
provider.getIfAvailable(DefaultPaymentService::new);
provider.stream()
.forEach(PaymentService::processPayment);
when(provider.getObject()).thenReturn(mockPaymentService);
โ Easy to mock in unit tests
@Component
public abstract class ReportService {
public void generate() {
PrototypeBean bean = createPrototypeBean();
bean.generate();
}
@Lookup
protected abstract PrototypeBean createPrototypeBean();
}
โ Spring overrides method at runtime using CGLIB subclassing.
Singleton Bean
|
| calls getObject()
โ
Spring Container โ NEW Prototype instance
Spring creates subclass
|
Overrides method
|
Each call โ Container โ NEW Prototype
@Lookup is method injection. Spring overrides the method at runtime using CGLIB and performs a BeanFactory lookup every time the method is called.
@Lookup protected abstract PrototypeBean createPrototypeBean();
Internally behaves like:
@Override
protected PrototypeBean createPrototypeBean() {
return applicationContext.getBean(PrototypeBean.class);
}
Before annotations, lookup method injection was configured in XML:
<lookup-method name="createPrototypeBean" bean="prototypeBean"/>
Example:
<bean id="reportService" class="com.example.ReportService">
<lookup-method name="createPrototypeBean" bean="prototypeBean"/>
</bean>
| Modern | Legacy |
|---|---|
| @Lookup | <lookup-method> |
If two prototype beans exist:
@Component
@Scope("prototype")
public class PaymentService { }
@Component
@Scope("prototype")
public class StripePaymentService extends PaymentService { }
Using:
@Lookup protected abstract PaymentService createPaymentService();
Spring throws:
NoUniqueBeanDefinitionException: Expected single matching bean but found 2
Fix by specifying bean name:
@Lookup("stripePaymentService")
protected abstract PaymentService createPaymentService();
@Autowired private ObjectProvider<PaymentService> provider;
Throws exception if multiple beans exist.
provider.stream()
.forEach(PaymentService::processPayment);
โ Returns all matching beans
โ No exception
โ More flexible resolution
| Scenario | @Lookup | ObjectProvider |
|---|---|---|
| Single Bean | Works | Works |
| Multiple Beans | Exception (unless name specified) | Exception for getObject() |
| Stream All Beans | No | Yes |
| Optional Bean | No | Yes |
| Fallback Default | No | Yes |