Interviewers love this one because it tests whether you understand how Spring chooses a bean when there are several candidates, not just whether you can remember annotation names.
Question: Difference between @Primary and @Qualifier.
Answer: Both annotations help Spring choose the right bean for dependency injection when more than one bean has the same type. @Primary marks one bean as the default choice, while @Qualifier says exactly which bean I want at a specific injection point. In simple words: @Primary is the default winner, and @Qualifier is the exact name tag.
Interview-Ready Answer: I use @Primary when I want one bean to be the default candidate for a type, so Spring picks it if nothing else is specified. I use @Qualifier when I want to choose a specific bean at a specific injection point, usually by bean name. The key difference is that @Primary is a global default for that type, while @Qualifier is an explicit local override. If both apply, the qualifier is more specific and wins.
Detailed Explanation: In Spring Boot, both annotations solve the same basic problem: which bean should be injected when more than one bean matches the type? The difference is where you give the instruction. @Primary marks the default bean at the bean-definition level, while @Qualifier marks the exact bean at the injection-point level.
@Primary means: if Spring has to choose, prefer this bean.@Qualifier means: I want this exact bean here.smsNotificationSender, or a custom qualifier annotation in more advanced setups.| Aspect | @Primary | @Qualifier |
|---|---|---|
| Meaning | Default bean | Specific bean |
| Where applied | Bean definition | Injection point |
| Typical use | Fallback choice | Exact choice |
| Priority | Lower than qualifier | Overrides primary |
| Value style | Marker only | Name or tag |
@Primary.NoUniqueBeanDefinitionException.@Primary when one implementation is the common default and most services should get it without extra noise.@Qualifier when different services need different implementations, or when the choice depends on business meaning.@Primary does not pick a bean for collection injections like List<PaymentGateway>; Spring injects all matching beans there.@Primary for the same type, Spring cannot use one as a unique default and startup can fail with ambiguity.@Primary is stable across modern Spring Framework and Spring Boot 2.x and 3.x. Boot creates more beans through auto-configuration, so these rules matter even more, but the resolution logic stays the same.Performance note: Bean selection is very fast. In practice, Spring only compares the small set of candidates for one type, often 2 to 5 beans in a real application. So the complexity is roughly O(n) for that type’s candidates, but the constant cost is tiny and happens at startup or injection time, not on every business method call.
Memory rule: Do not use @Primary to hide a bad design. If two beans represent truly different business choices, a qualifier is clearer because it documents the intent right where the choice matters.
Real-World Example: Imagine a checkout service in an e-commerce system with two payment implementations: an internal gateway for store credit and an external gateway for cards. The team marks the internal gateway as @Primary so most services get the default path, but the card-payment flow uses @Qualifier to request the external gateway explicitly.
What goes wrong when someone misunderstands this? A developer injects PaymentGateway by type into a refund service and assumes Spring will pick the card provider. Because the internal gateway is primary, refunds are sent to the wrong system, and the logs show the wrong class name being used. Users see failed refunds, support sees a spike in tickets, and the fix is usually either adding the right qualifier or removing the accidental primary from the wrong bean.
That is why this topic matters in production: the error is not a syntax mistake, it is a routing mistake.
package com.example.demo;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@SpringBootApplication
public class PrimaryQualifierApplication implements CommandLineRunner {
private final AlertService alertService;
private final ApplicationContext applicationContext;
public PrimaryQualifierApplication(AlertService alertService, ApplicationContext applicationContext) {
this.alertService = alertService;
this.applicationContext = applicationContext;
}
public static void main(String[] args) {
SpringApplication.run(PrimaryQualifierApplication.class, args);
}
@Override
public void run(String... args) {
System.out.println(alertService.sendDefaultAlert("Database latency is high"));
System.out.println(alertService.sendSmsAlert("Database latency is high"));
// Edge case: asking for a bean name that does not exist fails fast.
// This is the sort of mistake @Qualifier prevents when there are many candidates.
try {
applicationContext.getBean("pushNotificationSender");
} catch (NoSuchBeanDefinitionException ex) {
System.out.println("Lookup failed: " + ex.getClass().getSimpleName() + " - " + ex.getMessage());
}
// If @Primary were removed from EmailNotificationSender, the constructor injection
// for defaultSender would become ambiguous and Spring would fail at startup.
}
}
interface NotificationSender {
String send(String message);
}
@Service
@Primary
class EmailNotificationSender implements NotificationSender {
@Override
public String send(String message) {
return "Email sent: " + message;
}
}
@Service("smsNotificationSender")
class SmsNotificationSender implements NotificationSender {
@Override
public String send(String message) {
return "SMS sent: " + message;
}
}
@Service
class AlertService {
private final NotificationSender defaultSender;
private final NotificationSender smsSender;
AlertService(NotificationSender defaultSender,
@Qualifier("smsNotificationSender") NotificationSender smsSender) {
this.defaultSender = defaultSender;
this.smsSender = smsSender;
}
String sendDefaultAlert(String message) {
// No qualifier here: Spring picks the @Primary bean for this type.
return defaultSender.send(message);
}
String sendSmsAlert(String message) {
// @Qualifier narrows the choice to the bean with this exact name.
return smsSender.send(message);
}
}Follow-up & Tricky Questions:
@Primary does not filter the collection. If you need only one bean, inject a single bean or use a qualifier.@Qualifier is the bean name, and that is why naming beans clearly matters.@Qualifier. For interviews and production code, prefer the annotation when the choice matters.Common Mistakes:
@Primary and @Qualifier are interchangeable. Correction: @Primary is the default fallback, while @Qualifier is the exact choice at a specific injection point.@Primary to choose one bean from a list injection. Correction: Collections get all matching beans, so primary is ignored there.service1 and service2. Correction: Good names make qualifiers readable and make code self-explanatory.Memory Hook: Primary is the default taxi at the rank; Qualifier is the taxi number you booked. Default choice versus exact reservation.
Cheat Sheet:
@Primary = default bean for a type.@Qualifier = exact bean at the injection point.List or Map injections.Practice Tasks:
NotificationSender bean and inject it with a new qualifier.@Primary and observe the ambiguity error at startup, then add it back.@Qualifier to a wrong value and see how Spring fails fast.