A logo showing the text blog.marcnuri.com
Español
Home»Backend Development»Spring Bean Scopes: Singleton with Prototypes

Recent Posts

  • Eclipse JKube 1.19 is now available!
  • 2025 Year in Review: The Year of AI
  • Synology DS224+: How to upgrade hard drives in RAID 1
  • Fabric8 Kubernetes Client 7.5 is now available!
  • Boosting My Developer Productivity with AI in 2025

Categories

  • Artificial Intelligence
  • Backend Development
  • Cloud Native
  • Engineering Insights
  • Frontend Development
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Quality Engineering
  • Tools

Archives

  • February 2026
  • January 2026
  • December 2025
  • October 2025
  • September 2025
  • July 2025
  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • March 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • October 2017
  • August 2017
  • July 2017
  • January 2017
  • December 2015
  • November 2015
  • December 2014
  • November 2014
  • October 2014
  • March 2014
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

Spring Bean Scopes: Singleton with Prototypes

2018-05-11 in Backend Development tagged Bean / Java / Scopes / SOLID / Spring Framework / Spring Boot / Testing by Marc Nuri | Last updated: 2026-02-16
Versión en Español

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:

ApproachBest forTrade-off
ObjectProviderMost casesRequires code changes
ObjectFactorySimple scenariosFewer features
@LookupComponent-scanned beansWon't work with @Bean
Scoped ProxyRequest/Session scopeNew 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(): Returns null or a default value if the bean doesn't exist
  • getIfUnique(): Returns the bean only if exactly one candidate exists
  • stream(): 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.INTERFACES for 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:

ApproachWhen to use
ObjectProviderDefault choice: clean, testable, flexible
ObjectFactoryWhen you only need getObject()
@LookupComponent-scanned beans where you want minimal code
Scoped ProxyRequest/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.

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Comments in "Spring Bean Scopes: Singleton with Prototypes"

  • Avatar for Sainath
    Sainath
    2020-03-31 10:32
    Hi Marc,

    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

Post navigation
Spring Boot: How to change the application port?Spring Bean Scopes: Guide to understand the different Bean scopes
© 2007 - 2026 Marc Nuri