Interview Prep Zoneby CuriouserLabs

Question 5 of 5 · senior level

Multiple beans implement the same interface. What are your tools to control which gets injected?

🎤 Say this first

In order of preference: inject the collection (List<PaymentProvider> / Map<String, PaymentProvider>) when 'all of them' is the real requirement (strategy registries); @Qualifier (ideally a custom qualifier annotation, type-safe) to pick one; @Primary to define the default with others opt-in; ObjectProvider for optional/deferred resolution; @Order/Ordered to control collection ordering. Falling back on field-name matching is fragile — never rely on it.

The full picture

The strategy-registry idiom
interface PaymentProvider { String key(); Receipt charge(Order o); }

@Component class StripeProvider implements PaymentProvider { … }
@Component class AdyenProvider  implements PaymentProvider { … }

@Service
class PaymentRouter {
    private final Map<String, PaymentProvider> byKey;
    PaymentRouter(List<PaymentProvider> providers) {      // ALL impls injected
        this.byKey = providers.stream()
            .collect(toMap(PaymentProvider::key, p -> p));
    }
    Receipt charge(String provider, Order o) {
        return byKey.get(provider).charge(o);             // open/closed: new provider
    }                                                      // = new @Component, zero edits
}

// Type-safe qualifier beats string matching:
@Qualifier @Retention(RUNTIME) @interface Sandbox {}
@Component @Sandbox class SandboxProvider implements PaymentProvider { … }
PaymentRouter(@Sandbox PaymentProvider p) { … }
  • Map<String, T> injection keys by bean name — handy, but a custom key() method survives renames better.
  • @Primary = 'the default'; @Qualifier = 'this specific one'; combining them models default-plus-overrides cleanly (e.g., primary real client, qualified test double).
  • ObjectProvider<T> handles optional deps (getIfAvailable), lazy resolution, and streams of candidates without container exceptions.
  • Profiles/@ConditionalOn… remove ambiguity at registration time rather than injection time — often the cleanest cut.

🔄 Likely follow-up questions

  • How does @Order interact with List injection ordering?
  • Generics as qualifiers: how does Spring match Converter<String, Foo>?
  • When do you drop to ApplicationContext.getBeansOfType and why is it a smell in business code?