Walk through Spring's full candidate-resolution algorithm when multiple beans match an injection point.
answer
- type → qualifier → fallback-separation → primary → priority → name
- @Primary before @Priority
- @Priority lower number wins
- @Fallback dropped if any regular exists
- collections skip disambiguation
basics
~20 sSpring collects all beans of the required type. It filters by @Qualifier, then prefers a @Primary bean, then the highest @Priority, then a bean whose name matches the injection point. If none of those uniquely picks one, it throws NoUniqueBeanDefinitionException.
solid answer
~40 sFor a single-value injection, Spring first gathers every bean assignable to the required type. Any @Qualifier at the injection point (and required=true/generics constraints) filters that set. Then determineAutowireCandidate runs: (1) if exactly one remaining candidate is @Primary, choose it — multiple primaries throw; (2) otherwise pick the highest-priority candidate via jakarta.annotation.Priority (lowest number = highest priority) — a tie between equal priorities throws; (3) otherwise fall back to matching the field/parameter name against candidate bean names. If none of these yields a unique winner, Spring raises NoUniqueBeanDefinitionException. In Spring 6.2, @Fallback candidates are set aside first: if any regular (non-fallback) candidate exists, fallbacks are removed before this algorithm runs, so a regular bean wins over a fallback without needing @Primary. Collections and Maps skip disambiguation entirely and receive all matches.
code
java · 16 linesimport jakarta.annotation.Priority;
interface Notifier { }
@Service @Priority(10) // lower number = higher priority
class EmailNotifier implements Notifier { }
@Service @Priority(20)
class SmsNotifier implements Notifier { }
@Service
class AlertService {
// No @Qualifier, no @Primary among candidates ->
// @Priority decides: EmailNotifier (10) wins over SmsNotifier (20)
AlertService(Notifier notifier) { }
}go deeper
Knows the container throws when it can't pick one bean.
Can name @Qualifier, @Primary, and the exception, roughly in order.
Recites the full precedence including @Priority semantics and collection behavior.
Adds @Fallback's placement in the pipeline and reasons about deterministic wiring in large apps.
## Step 0 — type match When resolving `@Autowired T dep`, Spring asks the `DefaultListableBeanFactory` for all bean names whose bean is assignable to `T` (honoring generics, e.g. `Repository<Order>`). This yields the **candidate set**. - **0 candidates** → `NoSuchBeanDefinitionException` (unless the dependency is optional). - **1 candidate** → done. - **>1 candidate** → disambiguation runs. ## Step 1 — qualifier / attribute filtering If the injection point carries `@Qualifier` (string or custom) or other qualifier annotations, `QualifierAnnotationAutowireCandidateResolver` removes candidates that don't match. Generic type arguments also filter here. After this, if exactly one remains, it's chosen. ## Step 2 — @Fallback separation (Spring Framework 6.2+) Candidates are partitioned into **regular** and **@Fallback** beans. If at least one regular candidate exists, **all fallback candidates are discarded**. Fallbacks only survive when there are no regular candidates. (Before 6.2 this step didn't exist.) ## Step 3 — determineAutowireCandidate With multiple candidates still standing, `DefaultListableBeanFactory.determineAutowireCandidate` applies, in order: 1. **@Primary** — `determinePrimaryCandidate`. If exactly one candidate is primary, return it. If **two or more** are primary, throw `NoUniqueBeanDefinitionException` ("more than one 'primary' bean"). 2. **@Priority** — `determineHighestPriorityCandidate`, using `jakarta.annotation.Priority` (or `javax.annotation.Priority`). **Lower value = higher priority.** If two candidates share the same highest priority, throw `NoUniqueBeanDefinitionException`. Note: `@Priority` is read from the bean's **type**, and it is *not* supported on `@Bean` factory methods the way `@Primary` is — it must be on the component class. 3. **Bean-name fallback** — `matchesBeanName`: compare the injection point's field/parameter name to each candidate's bean name (and aliases). If one matches, return it. ## Step 4 — give up If none of the above uniquely resolves, Spring throws `NoUniqueBeanDefinitionException` listing the competing candidates. ## Collections, arrays, maps Injecting `List<T>`, `T[]`, `Set<T>`, or `Map<String,T>` (name→bean) **bypasses disambiguation** — you get *all* matching beans. `@Primary`/`@Qualifier` don't reduce these (though a `@Qualifier` can still restrict which beans go into the collection when candidates carry matching qualifiers). Ordering of `List`/array respects `@Order`/`Ordered`. ## Key precedence takeaways - `@Qualifier` filters **before** `@Primary` — so a qualifier overrides a primary. - `@Primary` is checked **before** `@Priority`. - Bean-name matching is the **last** resort. - `@Fallback` removal happens **before** primary/priority — a fallback never competes when a regular bean is present. ## Common pitfalls - Expecting `@Priority` to override `@Primary` — it's the reverse. - Putting `@Priority` on a `@Bean` method and expecting it to be honored. - Relying on name fallback and then renaming a parameter, silently changing wiring. - Assuming `List<T>` injection respects `@Primary` (it doesn't).
- Between @Primary and @Priority, which takes precedence?@Primary. Spring checks for a single primary candidate first; only if there is none does it consult @Priority (lowest number wins).
- Does @Priority work when placed on a @Bean factory method?No. @Priority is resolved from the target bean type/class, not from @Bean methods. Use @Primary (which does work on @Bean methods) or a qualifier for method-defined beans.
saying these in an interview costs you the question
- Saying @Priority overrides @Primary
- Thinking a higher @Priority number means higher priority
- Claiming @Primary filters List<T> injections
- Believing bean-name matching is tried before @Primary