Card 04/ 07

GotchaDifficulty: Intermediate1 min

Two Beans, One Interface, No Winner

Add a second implementation of NotificationSender, and the application that was starting fine a moment ago refuses to start at all.

java
interface NotificationSender { void send(String message); }

@Component("smsSender")
class SmsNotificationSender implements NotificationSender { /* ... */ }

@Component("emailSender")
class EmailNotificationSender implements NotificationSender { /* ... */ }

@Service
class RideAlertService {
    private final NotificationSender sender;
    RideAlertService(NotificationSender sender) { this.sender = sender; }
}
text
UnsatisfiedDependencyException: Error creating bean with name 'rideAlertService': ...
No qualifying bean of type 'rides.NotificationSender' available:
expected single matching bean but found 2: emailSender,smsSender
the real exception, verified against the two-implementation case

The trigger is reproducible and the mistake is easy to make without noticing: nothing about adding EmailNotificationSender touched RideAlertService, and yet RideAlertService is what refuses to build. Spring has two candidates for one constructor parameter and no rule for choosing between them, so it refuses to guess.

Two fixes, for two different situations. @Primary on one implementation says "use this one unless told otherwise" — right when one sender genuinely is the default. @Qualifier("emailSender") on the constructor parameter says "this specific caller wants this specific bean" — right when the choice depends on who's asking, not on which is generally preferred.

java
// @Primary: pick a default
@Component("smsSender")
@Primary
class SmsNotificationSender implements NotificationSender { /* ... */ }

// @Qualifier: this caller wants a specific one
@Service
class FixedByQualifierService {
    FixedByQualifierService(@Qualifier("emailSender") NotificationSender sender) { /* ... */ }
}
text
--- @Primary picks smsSender ---
sms: your ride has arrived
--- @Qualifier picks emailSender ---
email: your ride has arrived
both fixes, run in a container