In-Depth Guide to @Primary and @Qualifier Annotations with Practical Examples

@Qualifier
The @Qualifier annotation in Spring Boot is used to resolve ambiguity when Spring has multiple beans of the same type and it needs to know which one to inject. By default, Spring’s @Autowired annotation works by type, but when there are multiple beans of the same type, Spring needs a way to distinguish between them, and that’s where @Qualifier comes in.
1. Why Use @Qualifier?
Imagine you have multiple beans of the same type and want to autowire one of them into a class. If you don’t specify which bean to inject, Spring will throw an exception because it won’t know which one to choose. With @Qualifier, you can tell Spring exactly which bean to use.
2. Use Case Scenario
Let’s consider a scenario where you have two implementations of a service, say PaymentService, which processes payments via two methods: PayPal and Stripe. You want to autowire a specific implementation into your controller.
3. Example Code
Step 1: Define an Interface
public interface PaymentService {
String pay();
}Step 2: Create Multiple Implementations
import org.springframework.stereotype.Service;
@Service("paypalService")
public class PayPalPaymentService implements PaymentService {
@Override
public String pay() {
return "Payment processed via PayPal";
}
}
@Service("stripeService")
public class StripePaymentService implements PaymentService {
@Override
public String pay() {
return "Payment processed via Stripe";
}
}Here, we have two implementations of the PaymentService interface, one for PayPal and another for Stripe, each marked with a unique bean name using @Service("beanName").
Step 3: Use @Qualifier to Autowire the Correct Implementation
Now, let’s say you want to inject the PayPal implementation into your controller:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
private final PaymentService paymentService;
@Autowired
public PaymentController(@Qualifier("paypalService") PaymentService paymentService) {
this.paymentService = paymentService;
}
@GetMapping("/pay")
public String pay() {
return paymentService.pay();
}
}Here, we’ve used @Qualifier("paypalService") to tell Spring which implementation of PaymentService to inject into the PaymentController.
Step 4: Switch to Stripe Service (Optional)
If you want to switch to the Stripe service, simply change the qualifier:
@Autowired
public PaymentController(@Qualifier("stripeService") PaymentService paymentService) {
this.paymentService = paymentService;
}4. Testing with Postman
You can now test your API using Postman:
- Start your Spring Boot application.
- Open Postman.
- Send a GET request to
http://localhost:8080/pay.
If you’ve used the PayPal service (@Qualifier("paypalService")), the response will be:
{
"message": "Payment processed via PayPal"
}If you switch to the Stripe service (@Qualifier("stripeService")), the response will be:
{
"message": "Payment processed via Stripe"
}5. JUnit Testing Example
To test this behavior using JUnit, you can write a simple test case to verify the correct service is injected.
Test Class Example:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
public class PaymentControllerTest {
@Autowired
@Qualifier("paypalService")
private PaymentService paypalService;
@Autowired
@Qualifier("stripeService")
private PaymentService stripeService;
@Test
public void testPayPalService() {
assertEquals("Payment processed via PayPal", paypalService.pay());
}
@Test
public void testStripeService() {
assertEquals("Payment processed via Stripe", stripeService.pay());
}
}In this test, you verify that the correct service is autowired and behaves as expected.
Points to Remember about @Qualifier
- When to use it: Use
@Qualifierwhen you have multiple beans of the same type and need to specify which one should be injected. - How to use it: You can annotate the constructor, field, or method parameter with
@Qualifierto specify the bean name. - Combine with
@Autowired:@Qualifieris often used with@Autowiredto resolve injection conflicts.
This detailed example showcases how to use @Qualifier in a real-world scenario and how to test it with Postman and JUnit.
@Primary
The @Primary annotation in Spring Boot is used to specify a primary bean when there are multiple beans of the same type. If a class or method is annotated with @Primary, it will be selected as the default bean for dependency injection when no specific @Qualifier is used.
In contrast to @Qualifier, where you explicitly name which bean you want, @Primary serves as the default bean unless a more specific qualifier is provided.
1. Why Use @Primary?
When you have multiple beans of the same type and want to indicate which one should be the default, @Primary is useful. It allows Spring to inject a specific bean automatically without explicitly using @Qualifier every time. If no @Primary is set and there are multiple beans of the same type, Spring will throw an UnsatisfiedDependencyException.
2. Use Case Scenario
Let’s consider a scenario where you have two implementations of a service, say NotificationService, one for sending notifications via Email and another via SMS. You want Email to be the default notification service, but you might occasionally need to inject the SMS service explicitly.
3. Example Code
Step 1: Define an Interface
public interface NotificationService {
String notifyUser();
}Step 2: Create Multiple Implementations
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Service
@Primary
public class EmailNotificationService implements NotificationService {
@Override
public String notifyUser() {
return "User notified via Email";
}
}
@Service
public class SmsNotificationService implements NotificationService {
@Override
public String notifyUser() {
return "User notified via SMS";
}
}In this example:
- The
EmailNotificationServiceis marked with@Primary, so it will be the default bean when injectingNotificationService. - The
SmsNotificationServicewill be available but will only be injected if specified with@Qualifier.
Step 3: Injecting the Default (@Primary) Bean
In this controller, we inject the NotificationService without using @Qualifier, so Spring will automatically choose the EmailNotificationService (because of @Primary).
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotificationController {
private final NotificationService notificationService;
@Autowired
public NotificationController(NotificationService notificationService) {
this.notificationService = notificationService;
}
@GetMapping("/notify")
public String notifyUser() {
return notificationService.notifyUser();
}
}When you send a GET request to /notify, the response will be from the EmailNotificationService because it’s marked with @Primary.
Step 4: Switching to Another Bean Using @Qualifier
If you want to use the SMS service instead of the default Email service in another controller or method, you can use @Qualifier.
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SmsNotificationController {
private final NotificationService notificationService;
@Autowired
public SmsNotificationController(@Qualifier("smsNotificationService") NotificationService notificationService) {
this.notificationService = notificationService;
}
@GetMapping("/sms-notify")
public String notifyUserViaSms() {
return notificationService.notifyUser();
}
}Now, when you send a GET request to /sms-notify, the response will be from the SmsNotificationService.
4. Testing with Postman
You can test the behavior of @Primary and @Qualifier using Postman:
- For default service (
@Primary):
- Send a GET request to
http://localhost:8080/notify. - Response
{
"message": "User notified via Email"
}2. For explicitly qualified service (@Qualifier):
- Send a GET request to
http://localhost:8080/sms-notify. - Response
{
"message": "User notified via SMS"
}5. JUnit Testing Example
You can test which service is injected by default using JUnit:
Test Class Example:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
public class NotificationServiceTest {
@Autowired
private NotificationService notificationService; // This should inject the primary EmailNotificationService
@Test
public void testDefaultNotificationService() {
assertEquals("User notified via Email", notificationService.notifyUser());
}
}Here, notificationService will be injected with EmailNotificationService because of the @Primary annotation.
Points about @Primary
- Use Case: Use
@Primarywhen you want a default bean of a specific type to be injected automatically. - Multiple Beans:
@Primaryis helpful when there are multiple beans of the same type but you want one to be injected by default without needing to specify it explicitly every time. - Interaction with
@Qualifier: If a bean is annotated with@Primary, it will be injected by default unless@Qualifieris used to specify another bean explicitly.
Difference Between @Primary and @Qualifier

👏 If you found my articles useful, please consider giving it claps and sharing it with your friends and colleagues.
To read other topics
- Spring Boot Circuit Breaker Example with Resilience4j: Step-by-Step Guide
- Spring Boot Retry Pattern Example with Resilience4j: Step-by-Step Guide
- Method References in Java 8
- Mastering Transaction Propagation and Isolation in Spring Boot
- @Formula Annotation in Spring Boot
- In-Depth Guide to @Primary and @Qualifier Annotations with Practical Examples
- Understanding Logging in Spring Boot: A Complete Overview with Example
- Understanding Hash Collisions in Java’s HashMap
- Exploring Java Collections: A Guide to Lists, Sets, Queues, and Maps
- Using the @Temporal Annotation in Hibernate and Spring Boot
- Understanding CORS and CSRF in Spring Boot
- Complete CRUD Example in Spring Boot with DTO Validation, and Common API Response using MySQL
- Guide to Spring Boot Validation Annotations
- Mastering Git and GitHub Integration in IntelliJ IDEA
- Spring Boot Profiles
- Design Pattern in java
- ExecutorService, Thread Pools, Future Interface, Runnable, and Callable
- Comprehensive Guide to Java Concurrency: ExecutorService, Thread Pools, Future Interface, Runnable, and Callable





