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 SMSServiceambiguity 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.