Spring Bean Scopes: Singleton with Prototypes
Introduction
In the previous tutorial on bean scopes, I covered a common gotcha: when you inject a Prototype bean into a Singleton, the prototype behaves like a singleton. Why? Because Spring injects dependencies when it creates the Singleton, which happens exactly once.
This post presents four solutions to get true Prototype behavior inside a Singleton:
| Approach | Best for | Trade-off |
|---|---|---|
| ObjectProvider | Most cases | Requires code changes |
| ObjectFactory | Simple scenarios | Fewer features |
| @Lookup | Component-scanned beans | Won't work with @Bean |
| Scoped Proxy | Request/Session scope | New instance per method call |
Here's the problem code:
@Test
public void getAutowiredSample_na_shouldBeSameInstance() {
// Given
ClassWithSamplePrototypes singleton1 = applicationContext.getBean(ClassWithSamplePrototypes.class);
ClassWithSamplePrototypes singleton2 = applicationContext.getBean(ClassWithSamplePrototypes.class);
// When
Sample autowiredSample1 = singleton1.getAutowiredSample();
Sample autowiredSample2 = singleton2.getAutowiredSample();
// Then
assertEquals(singleton1, singleton2);
assertEquals(autowiredSample1, autowiredSample2);
assertEquals(autowiredSample1.getUuid(), autowiredSample2.getUuid());
}Despite Sample being defined with Prototype scope, both requests return the same instance.
The prototype was injected once when the Singleton was created, and that's the instance you'll always get.
@Lookup annotation
The @Lookup annotation tells Spring to override a method at runtime, delegating to BeanFactory.getBean().
Each call returns a fresh instance when the target bean has Prototype scope.
Here's the usage pattern:
@Lookup
public Sample getSampleUsingLookup() {
return null;
}Warning
@Lookup only works with beans discovered via component scanning (@Component and its specializations).
It won't work with @Bean methods because Spring needs to subclass the bean to override the method.
This test demonstrates @Lookup working correctly in a @Component bean:
@Test
public void getSampleUsingLookupInComponent_na_shouldBeDifferent() {
// Given
SingletonWithLookupPrototype singleton1 = applicationContext.getBean(SingletonWithLookupPrototype.class);
SingletonWithLookupPrototype singleton2 = applicationContext.getBean(SingletonWithLookupPrototype.class);
// When
Sample lookedupSample1 = singleton1.getSampleUsingLookup();
Sample lookedupSample2 = singleton2.getSampleUsingLookup();
// Then
assertEquals(singleton1, singleton2);
assertNotNull(singleton1);
assertNotNull(singleton2);
assertNotEquals(lookedupSample1, lookedupSample2);
assertNotEquals(lookedupSample1.getUuid(), lookedupSample2.getUuid());
}The singleton instances are identical (singleton1 == singleton2), but each call to getSampleUsingLookup() returns a fresh Sample instance.
However, if you define your bean using @Bean instead of @Component, the @Lookup method won't be overridden:
@Bean
@Scope(SCOPE_SINGLETON)
public ClassWithSamplePrototypes singletonWithSamplePrototypes(Sample autowiredSample, ObjectFactory<Sample> sampleObjectFactory) {
return new ClassWithSamplePrototypes(autowiredSample, sampleObjectFactory);
}The @Lookup method returns null because Spring didn't subclass the bean to override it:
@Test
public void getSampleUsingLookupInRegularBean_na_shouldBeNull() {
// Given
ClassWithSamplePrototypes singleton1 = applicationContext.getBean(ClassWithSamplePrototypes.class);
ClassWithSamplePrototypes singleton2 = applicationContext.getBean(ClassWithSamplePrototypes.class);
// When
Sample lookedupSample1 = singleton1.getSampleUsingLookup();
Sample lookedupSample2 = singleton2.getSampleUsingLookup();
// Then
assertEquals(singleton1, singleton2);
assertNull(lookedupSample1);
assertNull(lookedupSample2);
}Using ApplicationContext
Another approach is to inject ApplicationContext into the Singleton and call getBean() whenever you need a new Prototype instance:
/** ... **/
public class ClassWithSamplePrototypes implements ApplicationContextAware {
/** ... **/
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/** ... **/
public Sample getSampleUsingApplicationContext() {
return applicationContext.getBean(Sample.class);
}
/** ... **/
}We can verify this works with a test:
@Test
public void getSampleUsingApplicationContext_na_shouldBeDifferent() {
// Given
ClassWithSamplePrototypes singleton1 = applicationContext.getBean(ClassWithSamplePrototypes.class);
ClassWithSamplePrototypes singleton2 = applicationContext.getBean(ClassWithSamplePrototypes.class);
// When
Sample applicationContextSample1 = singleton1.getSampleUsingApplicationContext();
Sample applicationContextSample2 = singleton2.getSampleUsingApplicationContext();
// Then
assertEquals(singleton1, singleton2);
assertNotEquals(applicationContextSample1, applicationContextSample2);
assertNotEquals(applicationContextSample1.getUuid(), applicationContextSample2.getUuid());
}This approach works, but it couples your code directly to Spring's container.
Dependencies are requested rather than injected, which contradicts inversion of control.
For better testability and looser coupling, consider using ObjectProvider or ObjectFactory instead.
ObjectFactory and ObjectProvider (recommended)
The cleanest solution is to inject a factory instead of the bean itself.
Spring provides ObjectFactory and its more powerful extension ObjectProvider.
Tip
ObjectProvider is the preferred choice since Spring 4.3.
It adds methods like getIfAvailable(), getIfUnique(), and stream() that make working with optional or multiple beans much cleaner.
ObjectFactory (basic approach)
ObjectFactory is a simple functional interface with a single method: getObject().
Inject it instead of the bean itself, and Spring will provide a factory that delegates to the container:
public class SingletonWithObjectFactory {
private final ObjectFactory<Sample> sampleFactory;
public SingletonWithObjectFactory(ObjectFactory<Sample> sampleFactory) {
this.sampleFactory = sampleFactory;
}
public Sample getNewSample() {
return sampleFactory.getObject();
}
}Each call to sampleFactory.getObject() returns a fresh instance when the target bean has Prototype scope.
ObjectProvider (preferred approach)
ObjectProvider extends ObjectFactory with additional convenience methods:
public class SingletonWithObjectProvider {
private final ObjectProvider<Sample> sampleProvider;
public SingletonWithObjectProvider(ObjectProvider<Sample> sampleProvider) {
this.sampleProvider = sampleProvider;
}
public Sample getNewSample() {
return sampleProvider.getObject();
}
// ObjectProvider has additional useful methods:
public Sample getOrDefault(Sample defaultSample) {
return sampleProvider.getIfAvailable(() -> defaultSample);
}
}Why prefer ObjectProvider?
getIfAvailable(): Returnsnullor a default value if the bean doesn't existgetIfUnique(): Returns the bean only if exactly one candidate existsstream(): Iterate over all matching beans- Supports optional dependencies without throwing exceptions
Instead of injecting the bean directly, you inject a factory interface.
Each call to getObject() delegates to the container, returning a fresh instance for Prototype beans.
Here's the test:
@Test
public void getSampleUsingObjectProvider_na_shouldBeDifferent() {
// Given
SingletonWithObjectProvider singleton1 = context.getBean(SingletonWithObjectProvider.class);
SingletonWithObjectProvider singleton2 = context.getBean(SingletonWithObjectProvider.class);
// When
Sample sample1 = singleton1.getNewSample();
Sample sample2 = singleton2.getNewSample();
// Then
assertEquals(singleton1, singleton2);
assertNotEquals(sample1, sample2);
assertNotEquals(sample1.getUuid(), sample2.getUuid());
}The beauty of this approach is testability. You can easily mock the factory in unit tests without Spring:
new SingletonWithObjectFactory(() -> new Sample());Scoped Proxy
With scoped proxies, Spring injects a CGLIB proxy instead of the actual bean. The proxy intercepts method calls and delegates to a fresh instance each time.
This works transparently; the consuming class doesn't need any changes:
@Configuration
public class AppConfig {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public Sample sample() {
return new Sample();
}
@Bean
public SingletonWithProxy singletonWithProxy(Sample sample) {
return new SingletonWithProxy(sample);
}
}The singleton class doesn't need any special handling:
public class SingletonWithProxy {
private final Sample sample;
public SingletonWithProxy(Sample sample) {
this.sample = sample;
}
public String getSampleUuid() {
// Each call to the proxy creates a new prototype instance
return sample.getUuid();
}
}Pros:
- Zero code changes in the consuming class
- Simple annotation-based configuration
Cons:
- Every method call creates a new instance (can be wasteful for Prototype)
- Requires CGLIB (or use
ScopedProxyMode.INTERFACESfor JDK proxies)
Tip
Scoped proxies shine with Request and Session scoped beans, where the proxy transparently resolves the correct instance for the current HTTP context.
Frequently asked questions
Which approach should I use?
Start with ObjectProvider.
It's the most flexible, testable, and idiomatic approach.
Use scoped proxies for Request/Session beans where you want transparent resolution.
Will @Lookup work with @Bean methods?
No. Spring needs to subclass the bean to override the @Lookup method, which only happens with component-scanned beans.
For @Bean definitions, use ObjectProvider instead.
What's the performance impact of these approaches?
All approaches add minimal overhead.
ObjectProvider and ObjectFactory involve a getBean() call.
Scoped proxies add method interception via CGLIB.
In practice, the difference is negligible unless you're calling thousands of times per second.
Can I use these techniques with Request or Session scoped beans?
Yes, and scoped proxies are particularly useful here. They automatically resolve the correct bean for the current HTTP request or session without explicit lookup code.
Conclusion
When you inject a Prototype bean into a Singleton, Spring's default behavior defeats the purpose of Prototype scope. Here's the summary:
| Approach | When to use |
|---|---|
| ObjectProvider | Default choice: clean, testable, flexible |
| ObjectFactory | When you only need getObject() |
| @Lookup | Component-scanned beans where you want minimal code |
| Scoped Proxy | Request/Session beans needing transparent resolution |
For most cases, ObjectProvider is my recommendation.
It keeps your code testable, doesn't couple you to Spring's internals, and provides useful methods for optional dependencies.
For more on injection patterns and testing Spring applications, check out my other tutorials.
The full source code with runnable JBang examples is available on GitHub.

Comments in "Spring Bean Scopes: Singleton with Prototypes"
That's an excellent article and I have followed it and it worked for me.
Now I have a scenario where I want to access the particular prototype bean through singleton bean or let's say I want to track all prototype beans through singleton bean.
Is it possible?
Thanks for your time