Spring provides multiple ways to resolve dependency injection conflicts when multiple beans of the same type exist. Two commonly used annotations to handle this are @Primary
and @Qualifier
.
Letβs dive into the difference between them, their usage with examples, and best practices.
π§ Problem Scenario
Letβs say you have an interface MessageService
and two implementations: EmailService
and SMSService
.
public interface MessageService {
void sendMessage(String message);
}
@Component
public class EmailService implements MessageService {
public void sendMessage(String message) {
System.out.println("Sending Email: " + message);
}
}
@Component
public class SMSService implements MessageService {
public void sendMessage(String message) {
System.out.println("Sending SMS: " + message);
}
}
Now, when Spring tries to autowire MessageService
, it doesnβt know whether to inject EmailService
or SMSService
βambiguity arises.
β
Solution 1: @Primary
β Mark One as the Default
Use @Primary
when one implementation should be the default.
@Component
@Primary
public class EmailService implements MessageService {
public void sendMessage(String message) {
System.out.println("Sending Email: " + message);
}
}
@Component
public class SMSService implements MessageService {
public void sendMessage(String message) {
System.out.println("Sending SMS: " + message);
}
}
@Component
public class NotificationSender {
private final MessageService messageService;
@Autowired
public NotificationSender(MessageService messageService) {
this.messageService = messageService;
}
public void send(String message) {
messageService.sendMessage(message);
}
}
π Output:
Sending Email: Hello from @Primary
β
Solution 2: @Qualifier
β Be Explicit
Use @Qualifier
when you want to be specific about which implementation to inject.
@Component("emailService")
public class EmailService implements MessageService {
public void sendMessage(String message) {
System.out.println("Sending Email: " + message);
}
}
@Component("smsService")
public class SMSService implements MessageService {
public void sendMessage(String message) {
System.out.println("Sending SMS: " + message);
}
}
Now inject with @Qualifier
:
@Component
public class NotificationSender {
private final MessageService messageService;
@Autowired
public NotificationSender(@Qualifier("smsService") MessageService messageService) {
this.messageService = messageService;
}
public void send(String message) {
messageService.sendMessage(message);
}
}
π Output:
Sending SMS: Hello from @Qualifier
βοΈ @Primary
vs @Qualifier
β When to Use What?
Criteria | @Primary |
@Qualifier |
---|---|---|
Default choice | β Yes | β No |
Explicit selection | β No | β Yes |
Suitable for | One most-used implementation | Multiple specific implementations |
Use Case | App has a preferred/default bean | You switch between multiple beans frequently |
Can be overridden? | β
Yes, with @Qualifier
|
β Always explicit |
π‘ Best Practices
- Use
@Primary
only when one implementation should be used in most cases. -
Use
@Qualifier
when:- You need fine-grained control.
- You inject different implementations in different places.
Avoid combining both unless necessary (e.g., default with override).
π§ͺ Bonus: Use with @Bean
methods too!
@Configuration
public class AppConfig {
@Bean
@Primary
public MessageService emailService() {
return new EmailService();
}
@Bean
public MessageService smsService() {
return new SMSService();
}
}
You can use the same @Primary
and @Qualifier("smsService")
approach with @Bean
methods.
π Conclusion
- Use
@Primary
to define the default bean when multiple candidates exist. - Use
@Qualifier
when you need precise control. - Theyβre not mutually exclusiveβuse both smartly based on your project needs.
Top comments (0)