@Primary
About
The @Primary
annotation in Spring Framework is used to resolve ambiguity when multiple beans of the same type are available in the Spring ApplicationContext and no explicit qualifier is provided during injection.
It tells Spring, “If multiple matching candidates exist, prefer this one by default unless another is explicitly specified with @Qualifier
.”
In dependency injection, Spring matches a required bean type to the available beans in the container.
When multiple beans of the same type exist, Spring must decide which one to inject.
Without further guidance, this situation triggers a NoUniqueBeanDefinitionException
.
@Primary
is one way to guide Spring’s choice:
It marks one bean as the “default” choice among beans of the same type.
When injecting without qualifiers, the
@Primary
bean is injected.It does not prevent other beans from being injected explicitly if a
@Qualifier
is used.
Attributes
The @Primary
annotation itself does not define any attributes or parameters.
Its behavior is entirely declarative - simply placing it on a bean definition marks that bean as the preferred candidate when multiple beans of the same type are eligible for injection.
Syntax
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}
ElementType.TYPE
- can be placed at the class level (on@Component
,@Service
, etc.).ElementType.METHOD
- can be placed on@Bean
methods inside configuration classes.
Usage Locations
Class Level (with stereotype annotations)
@Component @Primary public class FastPaymentService implements PaymentService { }
Bean Method Level (in
@Configuration
class)@Configuration public class AppConfig { @Bean @Primary public PaymentService fastPaymentService() { return new FastPaymentService(); } }
Notes
One Primary per Injection Type
We can mark multiple beans as
@Primary
if they are of different types.If two beans of the same type are marked
@Primary
, Spring cannot choose and will throw an exception.
Interaction with
@Qualifier
If a
@Qualifier
is used at the injection site,@Primary
is ignored entirely for that injection.@Primary
is only a default qualifiers always override it.
With
@Profile
We can have different beans marked
@Primary
in different profiles to change the default at runtime.
Meta-Annotations
We can create a custom annotation that includes
@Primary
to apply it implicitly:@Primary @Service public @interface DefaultService { }
How It Works ?
When Spring performs dependency injection, it follows a multi-step candidate resolution process to determine which bean should be injected.
@Primary
participates in this process as a tie-breaker when multiple beans of the same type are found and no explicit @Qualifier
is provided.
1. Bean Discovery and Type Matching
When a dependency is requested (e.g., @Autowired PaymentService paymentService;
), Spring:
Scans the ApplicationContext for all beans assignable to the requested type (
PaymentService
in this example).Creates a list of all matching candidates.
2. Candidate Filtering
Before @Primary
comes into play, Spring filters candidates based on:
Bean definition scope (
singleton
,prototype
, etc.)Bean lifecycle state (e.g., fully initialized, not destroyed)
Any applied conditions (
@Conditional
,@Profile
, etc.)
3. Resolving Ambiguity Without Qualifiers
If multiple candidates remain after filtering:
Step 1: Check if one candidate is marked
@Primary
.If exactly one is found, Spring injects that bean.
If more than one is marked
@Primary
for the same type, Spring throws aNoUniqueBeanDefinitionException
.
Step 2: If no
@Primary
is found, Spring throwsNoUniqueBeanDefinitionException
unless the developer uses a@Qualifier
or bean name injection.
4. Interaction with @Qualifier
@Qualifier
If a
@Qualifier
is provided, it overrides any@Primary
designation.@Primary
is only considered when injection is based solely on type.
@Autowired
private PaymentService paymentService; // @Primary considered here
@Autowired
@Qualifier("slowPaymentService")
private PaymentService paymentService; // @Primary ignored
5. Resolution Inside Spring Internals
Internally, the decision-making occurs in:
DefaultListableBeanFactory
- the core bean factory used by most Spring applications.Specifically, the method
determinePrimaryCandidate()
examines all matching beans and:Iterates over candidates.
Checks for
isPrimary()
in eachBeanDefinition
.Returns the single primary bean if found.
Throws an exception if more than one primary exists for the same type.
6. Special Considerations
Collections and Maps: When injecting a collection (
List<PaymentService>
),@Primary
does not affect which beans are included - all beans of that type are injected.Factory Beans: If the bean is a
FactoryBean
,@Primary
applies to the product bean type, not theFactoryBean
itself.Profiles and Conditions:
@Primary
beans can be active only in certain profiles, allowing different defaults in dev, test, and prod environments.
Best Practice
Using @Primary
effectively ensures clarity in bean resolution, avoids runtime errors, and keeps our dependency injection predictable. However, because it sets a global default for a type, it must be used carefully to avoid unintended injections.
1. Use @Primary
for the True Default Only
@Primary
for the True Default OnlyApply
@Primary
only to the bean that should be the default choice for most injection points.If different modules have different "defaults," prefer
@Qualifier
to avoid confusion.
@Component
@Primary
public class FastPaymentService implements PaymentService { }
2. Avoid Multiple Primaries for the Same Type
Multiple
@Primary
beans for the same type cause ambiguity and result inNoUniqueBeanDefinitionException
.If we must have environment-specific primaries, combine
@Primary
with@Profile
.
@Profile("prod")
@Primary
@Bean
public PaymentService fastPaymentService() { ... }
@Profile("test")
@Primary
@Bean
public PaymentService mockPaymentService() { ... }
3. Prefer @Qualifier
for Specific Cases
@Qualifier
for Specific CasesUse
@Primary
for the majority default case.Use
@Qualifier
for exceptions or special cases.
@Autowired
@Qualifier("slowPaymentService")
private PaymentService backupService;
4. Keep @Primary
Visible and Obvious
@Primary
Visible and ObviousIf possible, place
@Primary
on the class itself (for stereotype beans) rather than the bean definition method, so it’s easier to spot during code reviews.If we put it in
@Bean
methods, group related configurations together.
5. Avoid @Primary
in Shared Libraries
@Primary
in Shared LibrariesDon’t mark beans in reusable libraries as
@Primary
unless absolutely necessary it can unexpectedly override application-level defaults.
6. Combine with Conditional Beans for Flexibility
Use
@Conditional
or@Profile
to make@Primary
bean selection environment-aware.
7. Document Why a Bean is Primary
Always document why a particular bean is marked as
@Primary
so future maintainers understand its global effect.In large projects, add Javadoc or comments explaining its role.
8. Testing with @Primary
@Primary
In test contexts, mark mocks or stubs as
@Primary
to automatically replace the production default without touching injection code.
@TestConfiguration
public class TestConfig {
@Bean
@Primary
public PaymentService testPaymentService() {
return new MockPaymentService();
}
}
9. Avoid Overuse
Too many
@Primary
beans across modules lead to hidden resolution rules that are hard to track.If we find ourself using
@Primary
in multiple places for the same type, reconsider the design and possibly rely more on explicit qualifiers.
Example
@Primary
can be applied in different ways depending on whether we define beans via annotations, Java configuration, XML, or even in testing.
Below are all key usage patterns with explanations.
1. Class-Level @Primary
with Component Scanning
@Primary
with Component ScanningWhen using Spring’s stereotype annotations (@Component
, @Service
, @Repository
), we can mark a class as the default bean for its type.
public interface PaymentService {
void pay(double amount);
}
@Component
@Primary
public class FastPaymentService implements PaymentService {
@Override
public void pay(double amount) {
System.out.println("Processing payment instantly: " + amount);
}
}
@Component
public class SlowPaymentService implements PaymentService {
@Override
public void pay(double amount) {
System.out.println("Processing payment slowly: " + amount);
}
}
@Service
public class CheckoutService {
@Autowired
private PaymentService paymentService; // Injects FastPaymentService
}
Explanation
Two beans (
FastPaymentService
,SlowPaymentService
) implementPaymentService
.@Primary
tells Spring to injectFastPaymentService
unless explicitly overridden with a@Qualifier
.
2. Bean-Method-Level @Primary
in Java Configuration
@Primary
in Java ConfigurationWhen using @Configuration
with @Bean
methods, @Primary
can be applied at the method level.
@Configuration
public class PaymentConfig {
@Bean
@Primary
public PaymentService fastPaymentService() {
return new FastPaymentService();
}
@Bean
public PaymentService slowPaymentService() {
return new SlowPaymentService();
}
}
@Service
public class CheckoutService {
@Autowired
private PaymentService paymentService; // Injects fastPaymentService
}
Explanation
Even without stereotype annotations,
@Primary
works at bean definition level.The default bean (
fastPaymentService
) is used when no@Qualifier
is given.
3. @Primary
with @Qualifier
Override
@Primary
with @Qualifier
Override@Qualifier
takes precedence over @Primary
.
@Service
public class CheckoutService {
@Autowired
private PaymentService paymentService; // Injects primary bean
@Autowired
@Qualifier("slowPaymentService")
private PaymentService backupService; // Explicitly injects slowPaymentService
}
Explanation
paymentService
→ injected from the primary bean.backupService
→ injected from the explicitly named bean, ignoring@Primary
.
4. Environment-Specific Primary Bean with @Profile
@Profile
Mark different beans as primary for different environments.
@Configuration
public class PaymentProfileConfig {
@Bean
@Primary
@Profile("prod")
public PaymentService fastPaymentService() {
return new FastPaymentService();
}
@Bean
@Primary
@Profile("test")
public PaymentService mockPaymentService() {
return new MockPaymentService();
}
}
Explanation
In the
prod
profile, the production service is primary.In the
test
profile, a mock service becomes the primary bean for testing purposes.
5. Testing Context Override with @Primary
@Primary
In tests, we can override the primary bean with a mock.
@TestConfiguration
public class TestPaymentConfig {
@Bean
@Primary
public PaymentService mockPaymentService() {
return new MockPaymentService();
}
}
@SpringBootTest
@Import(TestPaymentConfig.class)
public class CheckoutServiceTest {
@Autowired
private CheckoutService checkoutService;
@Test
void testPayment() {
checkoutService.processPayment(100);
// Uses MockPaymentService instead of FastPaymentService
}
}
Explanation
By marking the mock as
@Primary
in test config, it automatically replaces the production default for the entire test.
6. @Primary
in Combination with Factory Beans
@Primary
in Combination with Factory BeansWhen using FactoryBean
, @Primary
applies to the product type, not the factory itself.
@Component
@Primary
public class PrimaryConnectionFactoryBean implements FactoryBean<Connection> {
@Override
public Connection getObject() {
return new Connection("Primary DB");
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
}
@Component
public class SecondaryConnectionFactoryBean implements FactoryBean<Connection> {
@Override
public Connection getObject() {
return new Connection("Secondary DB");
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
}
Explanation
Even though two factory beans exist, Spring sees them as providers of
Connection
.The
PrimaryConnectionFactoryBean
is chosen when injecting aConnection
.
Last updated