2.5 Disambiguation & Qualifiers
← Back to Chapter 2: Spring Beans & Dependency Injection
When multiple beans implement the same interface, Spring's autowiring by type encounters ambiguity (NoUniqueBeanDefinitionException). Spring provides precise annotations to disambiguate and prioritize candidates.
1. The Disambiguation Problem
Consider two payment gateway implementations:
public interface PaymentGateway { void pay(BigDecimal amount); }
@Component("stripe")
public class StripeGateway implements PaymentGateway { ... }
@Component("paypal")
public class PaypalGateway implements PaymentGateway { ... }
@Service
public class CheckoutService {
// FAILS AT STARTUP: NoUniqueBeanDefinitionException: expected single matching bean but found 2: stripe, paypal
public CheckoutService(PaymentGateway paymentGateway) { ... }
}
2. Solutions: @Primary vs @Qualifier
A. @Primary (Default Priority)
Designates one candidate as the default choice when no specific qualifier is requested:
@Component
@Primary
public class StripeGateway implements PaymentGateway { ... }
Now CheckoutService(PaymentGateway gateway) automatically receives StripeGateway.
B. @Qualifier (Explicit Target Selection)
Pinpoints the exact bean by identifier or custom qualifier name:
@Service
public class CheckoutService {
private final PaymentGateway paymentGateway;
public CheckoutService(@Qualifier("paypal") PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
}
3. Custom Qualifier Annotations
String qualifiers (@Qualifier("paypal")) can be error-prone due to typos. Modern Spring projects define custom qualifier annotations:
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface StripeProvider {}
// Apply to implementation
@Component
@StripeProvider
public class StripeGateway implements PaymentGateway { ... }
// Apply to injection point
@Service
public class CheckoutService {
public CheckoutService(@StripeProvider PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
}
4. Injecting Collections of Beans & @Order
If you want to invoke all implementations of an interface (e.g., executing a chain of validators), inject a List<T> or Map<String, T>:
@Service
public class ValidationPipeline {
private final List<Validator> validators;
// Spring injects all Validator beans sorted by @Order
public ValidationPipeline(List<Validator> validators) {
this.validators = validators;
}
public void validateAll(Order order) {
validators.forEach(v -> v.validate(order));
}
}
@Component
@Order(1) // Runs first
public class SecurityValidator implements Validator { ... }
@Component
@Order(2) // Runs second
public class InventoryValidator implements Validator { ... }