What is Qualifier
If a consumer injects MessageService without qualification:
🟢 Junior Level
@Qualifier is a Spring Framework annotation used to eliminate ambiguity during Dependency Injection (DI) by explicitly specifying which candidate bean should be injected when multiple beans of the same interface or class type exist in the ApplicationContext.
Code Example: Ambiguity Problem and Solution
public interface MessageService {
void sendMessage(String text);
}
@Component("emailService")
public class EmailService implements MessageService {
public void sendMessage(String text) { System.out.println("Email: " + text); }
}
@Component("smsService")
public class SmsService implements MessageService {
public void sendMessage(String text) { System.out.println("SMS: " + text); }
}
If a consumer injects MessageService without qualification:
// ❌ FAILS ON STARTUP with NoUniqueBeanDefinitionException!
@Service
public class NotificationManager {
public NotificationManager(MessageService messageService) { ... }
}
Adding @Qualifier resolves the target bean explicitly:
// ✅ SUCCESS: Explicitly requests the emailService bean
@Service
public class NotificationManager {
private final MessageService messageService;
public NotificationManager(@Qualifier("emailService") MessageService messageService) {
this.messageService = messageService;
}
}
🟡 Middle Level
Application Targets: Declaration Site vs Injection Site
The @Qualifier annotation can be applied at two distinct points in the configuration lifecycle:
- At the Injection Site: On a constructor parameter, field, or setter method to specify the desired dependency:
@Autowired @Qualifier("fastClient") private HttpClient httpClient; - At the Declaration Site: On a
@Componentclass or a@Beanfactory method to attach a custom qualifier alias. Crucially, the qualifier alias does not have to match the bean name:@Configuration public class NetworkConfig { @Bean("primaryHttpClient") // Bean Name = primaryHttpClient @Qualifier("fastClient") // Qualifier Alias = fastClient public HttpClient httpClient() { return new FastHttpClient(); } }At the injection site,
@Qualifier("fastClient")successfully resolves this bean, even though its registered bean name in the container isprimaryHttpClient.
Triad Comparison: @Qualifier vs @Primary vs @Resource
Understanding how these three annotations interact is a core interview topic:
| Annotation | Specification / Source | Primary Resolution Strategy | Precedence |
|---|---|---|---|
@Qualifier |
Spring (org.springframework.beans...) |
Resolves byType first, then filters candidates by qualifier tag or bean name |
Highest Priority (always overrides @Primary) |
@Primary |
Spring | Marks a bean as the global default for its type | Lower than @Qualifier (only active when no qualifier is requested) |
@Resource(name = "...") |
Jakarta EE (jakarta.annotation.Resource) |
Resolves strictly by bean name (byName) first; falls back to byType only if name is omitted |
Alternative Java EE standard |
🔴 Senior Level
Under the Hood: QualifierAnnotationAutowireCandidateResolver
Spring evaluates qualifiers using the internal engine QualifierAnnotationAutowireCandidateResolver inside DefaultListableBeanFactory:
- When candidate beans for a type are collected, the resolver invokes
checkQualifier(BeanDefinitionHolder bdHolder, Annotation annotation, ...): - It inspects the target bean’s
BeanDefinitionto determine whether an explicitAutowireCandidateQualifierdescriptor is attached. - Fallback Qualifier Matching: If no explicit qualifier attribute is defined on the bean, Spring treats the bean name (
beanName) and any registered aliases (aliases) as an implicit default qualifier. - If a custom qualifier annotation is used, the resolver inspects all annotation attributes reflectively.
Type-Safe Meta-Qualifiers with Attributes
Hardcoding string literals like @Qualifier("postgresReplica") creates runtime fragility: a typo in the string compiles cleanly but crashes application startup.
Constructing an Attribute-Driven Meta-Qualifier:
package com.example.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Qualifier // Marks this annotation as a Spring Meta-Qualifier
public @interface DatabaseTarget {
Engine engine();
boolean readOnly() default false;
enum Engine { POSTGRES, CLICKHOUSE, ORACLE }
}
Bean Definition and Injection:
@Configuration
public class DataSourceConfig {
@Bean
@DatabaseTarget(engine = DatabaseTarget.Engine.POSTGRES, readOnly = false)
public DataSource postgresMaster() {
return new HikariDataSource();
}
@Bean
@DatabaseTarget(engine = DatabaseTarget.Engine.POSTGRES, readOnly = true)
public DataSource postgresReplica() {
return new HikariDataSource();
}
}
@Service
public class AnalyticsReportingService {
private final DataSource replicaDataSource;
// Spring matches ALL attributes simultaneously: engine == POSTGRES AND readOnly == true!
public AnalyticsReportingService(
@DatabaseTarget(engine = DatabaseTarget.Engine.POSTGRES, readOnly = true) DataSource ds) {
this.replicaDataSource = ds;
}
}
Senior Engineering Benefits:
- Zero String Typo Bugs: Fully validated by the Java compiler at compile time.
- IDE Autocompletion: Enum values auto-complete inside
@DatabaseTarget(...). - Safe Refactoring: Renaming enum constants or annotations automatically updates all bean declarations and injection points.
4 Tricky Questions
1. What is the fundamental difference in dependency resolution between @Autowired + @Qualifier("db") and @Resource(name = "db")?
Answer:
@Autowired+@Qualifier("db"): Resolution begins by type (byType). Spring finds all beans assignable to the declared class or interface, and then filters that candidate set for beans possessing the qualifier or name"db".@Resource(name = "db"): (from Jakarta Annotations specification) Resolution begins strictly by name (byName). It directly queries the container for a bean registered under the exact bean name"db". Only if no name attribute is specified does@Resourcefall back to matching by type.
2. What happens if you specify @Qualifier("foo"), but no bean has an explicit qualifier “foo”, although a bean is named “foo”?
Answer:
The bean named "foo" will be successfully injected.
Inside Spring’s QualifierAnnotationAutowireCandidateResolver, if no explicit AutowireCandidateQualifier matches the given qualifier string, Spring falls back to matching the qualifier string against the bean’s registered name (beanName) and its aliases. The bean name acts as an implicit, default qualifier.
3. Can a single Spring bean have multiple distinct qualifiers, and how is that achieved?
Answer: Yes, a single bean can have multiple qualifiers.
While you cannot repeat the standard @Qualifier("name") annotation multiple times on the same element in earlier Spring versions, you can define multiple custom meta-qualifier annotations (e.g. @Offline, @Fast, @Secure), where each annotation is meta-annotated with @Qualifier. Annotating a bean class or @Bean method with multiple custom qualifier annotations allows that bean to be matched by any of those individual qualifiers at different injection points.
4. How does Spring match custom qualifier annotations that declare multiple attributes?
Answer: Spring matches all attributes simultaneously.
Inside QualifierAnnotationAutowireCandidateResolver, Spring iterates through every declared attribute method in the custom qualifier annotation. The candidate bean must declare the exact same annotation with identical values for every attribute. If even a single attribute differs (e.g. engine=POSTGRES matches, but readOnly=true vs readOnly=false), the bean is rejected as a candidate.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
@Qualifieris Spring’s primary tool for resolving bean ambiguity when multiple implementations of a type exist in theApplicationContext.
- Resolution Strategy: Resolves
byTypefirst, then narrows candidates by matching the qualifier tag or fallback bean name.- Precedence Rule:
@Qualifieralways overrides@Primary.- Dual Placement: Can be placed at the injection point (constructor/setter/field) and at the declaration site (
@Component/@Bean), allowing qualifier aliases that differ from bean names.- Best Practice: Use custom meta-qualifier annotations (meta-annotated with
@Qualifier) to provide compile-time type safety, avoid string typos, and support multi-attribute matching.
Qualifier Disambiguation Summary
| Feature | Behavior |
|---|---|
Overriding @Primary |
Always trumps @Primary |
| Fallback Matching | Matches bean ID if no explicit qualifier tag exists |
| Placement | Fields, Constructor Parameters, Setter Methods, @Bean Methods, @Component Classes |
| Multi-Attribute Matching | All custom annotation attributes must match simultaneously |
Red Flags (DO NOT Say)
- ❌ “@Primary has higher priority than @Qualifier.” (Explicit
@Qualifierselection always overrides@Primary). - ❌ “@Qualifier performs resolution by name without checking types.” (It evaluates type compatibility
byTypefirst). - ❌ “@Qualifier can only be used on fields with
@Autowired.” (It works seamlessly on constructor parameters, setter methods, and@Beanfactory methods). - ❌ “String literals in
@Qualifierare completely safe in enterprise apps.” (They are brittle; custom meta-qualifiers should be used to guarantee compile-time verification).