@Autowired, @Qualifier & @Primary
@Autowired, @Qualifier & @Primary
Spring's dependency injection becomes powerful the moment you stop manually wiring beans and let the framework resolve them automatically. The three annotations covered in this lesson — @Autowired, @Qualifier, and @Primary — are the core tools Spring provides for that resolution. Understanding not just how to use them, but when and why, will save you from the most common DI-related bugs and keep your configuration clean at scale.
How Spring Resolves a Dependency
When Spring encounters a dependency injection point — a constructor parameter, a setter, or a field — it follows a two-step lookup:
- Type match: find all beans in the application context whose type is compatible with the required type.
- Name match (tiebreaker): if exactly one candidate remains, use it. If multiple candidates remain, try matching the injection point's variable name to a bean name. If still ambiguous, throw
NoUniqueBeanDefinitionException.
The annotations in this lesson give you explicit control over each step of that process.
@Autowired — Marking an Injection Point
@Autowired (from org.springframework.beans.factory.annotation) tells Spring: fill this dependency from the context. You can place it on a constructor, a setter method, or directly on a field.
@Service class with a single constructor and no @Autowired at all.
The required = false attribute is important: it tells Spring not to fail startup if no matching bean is found. Use it for truly optional integrations (analytics, feature-flag providers) — never as a way to silence a missing mandatory dependency.
The Ambiguity Problem
As soon as you have more than one bean of the same type in the context, Spring cannot decide which one to inject and throws NoUniqueBeanDefinitionException. This is extremely common with interfaces:
Now injecting PaymentGateway anywhere is ambiguous. Spring finds two candidates and has no rule to choose between them. You have two tools to fix this: @Primary and @Qualifier.
@Primary — Designating the Default
@Primary marks one bean as the preferred candidate whenever its type is required but no explicit qualifier has been given. Think of it as setting the system-wide default.
Now any injection point that asks for a PaymentGateway — without further qualification — receives the StripeGateway:
@Primary provides false clarity — use @Qualifier throughout instead.
@Qualifier — Precise, Per-Site Selection
@Qualifier attaches a string label to a bean and requires that exact label at the injection point, bypassing the type-match ambiguity entirely. The label defaults to the class name with a lowercase first letter (the bean's default name), or you can set it explicitly.
At the injection point you repeat the qualifier:
Custom Qualifier Annotations — The Production Pattern
A custom qualifier annotation eliminates the string-literal problem entirely. You define a meta-annotated annotation once and use it as a type-safe qualifier everywhere:
Now the compiler enforces the contract. If @PayPalPayment is renamed or removed, every usage site fails to compile immediately.
Combining @Primary and @Qualifier
These two annotations work together predictably: @Qualifier always wins over @Primary. If a bean is marked @Primary but the injection point carries a @Qualifier, Spring ignores the primary designation and uses the qualifier to select the bean.
- No qualifier at injection site → use
@Primarybean (if one exists), else fail with ambiguity. - Qualifier present → match by qualifier, ignore
@Primaryentirely. - Neither → Spring falls back to matching the variable name against bean names (fragile; avoid relying on this).
Injecting All Candidates — Collections and Optional
Sometimes you want every implementation — for example, to fan out a notification to all registered channels. Spring handles this automatically when you inject a List or Map:
Injecting Map<String, NotificationSender> gives you the bean name as the key — handy when you need to select an implementation at runtime by name.
Summary
Use @Autowired (or simply a single-constructor class) to declare injection points. When multiple candidates of the same type exist, resolve the ambiguity with @Primary for a single default bean, or @Qualifier for per-site precision. Prefer custom qualifier annotations over string literals in any codebase you expect to maintain. And remember: @Qualifier always takes precedence over @Primary.