Bean Naming, Qualifiers & Multiple Beans
Bean Naming, Qualifiers & Multiple Beans
Spring lets you register as many beans of the same type as you like — and that is a feature, not a bug. You might have a DataSource bean for your primary database and another for a read replica, two RestTemplate beans configured with different timeouts, or several implementations of a PaymentGateway interface. The challenge shifts from how do I register a bean? to how does Spring know which bean to inject when there are several candidates? This lesson answers that question completely.
How Spring Names Beans by Default
Every bean has at least one name. When you declare a bean Spring derives a default name from the source:
- @Component (and stereotypes): the simple class name with its first letter lowercased.
OrderServicebecomes"orderService". - @Bean method: the method name. A method called
primaryDataSource()registers a bean named"primaryDataSource". - XML <bean>: the
idattribute, falling back to a generated name if omitted.
You can override the default name anywhere a name is derived:
BeanDefinitionOverrideException (Spring Boot 2.1+ by default). Always choose unique, descriptive names.
Injecting by Name with @Qualifier
When Spring resolves a dependency by type and finds more than one matching bean it throws NoUniqueBeanDefinitionException. The standard fix is @Qualifier, which tells Spring exactly which bean name to use:
@Qualifier works on constructor parameters, setter parameters, and field injections alike. The string value must exactly match a registered bean name or alias.
@Primary — Choosing a Default Winner
If one bean should be the default choice for the vast majority of injection points, annotate it with @Primary. Any injection point that does not carry a @Qualifier will receive the primary bean; injection points that do carry a @Qualifier still get exactly what they ask for.
@Primary without also giving your beans clear names is a recipe for confusion when a third bean is added later. Always name beans explicitly and use @Primary as the tiebreaker.
Custom Qualifier Annotations
Scattering string literals like "replicaDs" across dozens of injection points is fragile — a typo compiles fine but fails at runtime. The professional approach is a custom qualifier annotation:
Apply it at the bean definition and at the injection site:
Now refactoring the qualifier is a compile-time operation — rename the annotation and your IDE updates all usages. String typos are impossible.
Injecting All Beans of a Type
Sometimes you want every registered implementation, not just one. Spring will happily inject a List<T> or Map<String, T> containing all beans of type T:
When injecting a Map<String, T> the keys are the bean names, giving you runtime lookup by name without coupling to Spring APIs in your business logic:
Controlling Order in Collections — @Order and Ordered
When Spring populates a List<T>, the order of elements is not guaranteed unless you specify it. Use @Order on the bean class (or implement org.springframework.core.Ordered) to set a priority. Lower values come first:
@Qualifier resolution or @Primary elections. Misusing it to try to "override" primary candidates is a common mistake that leads to subtle bugs.
Resolving Beans Programmatically
In rare cases — dynamic dispatch, plugin architectures — you need to look up a bean by name or type at runtime. Inject ApplicationContext and call getBean():
This pattern is called the Service Locator. It is acceptable at the edges of an architecture (e.g., when the bean name comes from a database row or a user request), but using it in the middle of business logic tightly couples your code to Spring and makes unit testing harder. Prefer collection injection whenever all variants are known at startup.
Decision Guide
- One clear default, occasional exceptions: use
@Primary+@Qualifierat the few exception sites. - Two or more equally important beans: give each a clear name or custom qualifier annotation; no
@Primary. - Fan-out to all implementations: inject
List<T>orMap<String, T>. - Runtime dynamic dispatch: inject
Map<String, T>or fall back toApplicationContext.getBean(). - Type-safe refactoring at scale: replace string qualifiers with custom qualifier annotations.
Summary
Spring derives bean names automatically but lets you override them everywhere. When multiple beans of the same type exist, @Qualifier pins an injection to a specific name, and @Primary elects a default winner for unqualified sites. Custom qualifier annotations lift the disambiguation out of fragile strings and into the type system. For fan-out scenarios, injecting a List<T> or Map<String, T> is the cleanest approach, with @Order controlling list ordering. Together these tools let you manage any number of bean candidates without ambiguity and without coupling your business logic to Spring internals.