Your app compiles fine, but the moment Spring Boot tries to start, it throws NoUniqueBeanDefinitionException. Unlike NoSuchBeanDefinitionException, this one isn’t about a missing bean — it’s about having too many.

TLDR - Quick Fix

NoUniqueBeanDefinitionException means Spring found more than one bean that matches the type you’re injecting, and it doesn’t know which to pick. Fix it by telling Spring which one you want:

// ❌ PROBLEM: Two beans implement PaymentGateway
public interface PaymentGateway { void charge(int amountCents); }

@Service
public class StripeGateway implements PaymentGateway { /* ... */ }

@Service
public class PaypalGateway implements PaymentGateway { /* ... */ }

// Spring has no idea which one to inject here
@Service
public class CheckoutService {
    public CheckoutService(PaymentGateway gateway) { /* boom */ }
}
// ✅ FIX: Mark the default with @Primary
@Service
@Primary
public class StripeGateway implements PaymentGateway { /* ... */ }

@Service
public class PaypalGateway implements PaymentGateway { /* ... */ }

Quick diagnostic steps:

  1. Read the exception message — it lists the exact bean names it found
  2. Decide if you actually need multiple implementations or if one is a leftover
  3. Pick a resolution strategy: @Primary, @Qualifier, or a List<T>/Map<String, T> injection
  4. Check for accidental duplicate @Bean methods across @Configuration classes

Let’s go through the common causes and how to resolve each one.

Diagnostic Steps

The stack trace is actually pretty generous with details here. It looks something like this:

Parameter 0 of constructor in com.example.checkout.CheckoutService
required a single bean, but 2 were found:
	- stripeGateway: defined in file [.../StripeGateway.class]
	- paypalGateway: defined in file [.../PaypalGateway.class]

Action:
Consider marking one of the beans as @Primary, updating the consumer to
accept multiple beans, or using @Qualifier to identify the bean that
should be consumed

Before touching any code, read that message carefully. It tells you:

  • Which class is injecting the ambiguous type (CheckoutService)
  • Exactly which beans are competing (stripeGateway, paypalGateway)
  • Where each bean is defined

That last line matters more than it looks. Sometimes the “duplicate” bean isn’t a real second implementation — it’s the same class registered twice because of a copy-pasted @Configuration method or an auto-configuration class that Spring Boot pulled in from a starter dependency. Run through the diagnostic checklist below before you start slapping @Primary on things.

It’s worth pausing here on why Spring behaves this way at all. Unlike some DI frameworks that pick “the first one registered” or “the last one wins,” Spring deliberately refuses to guess. That’s a feature, not an annoyance — silently picking a winner between StripeGateway and PaypalGateway would mean your checkout flow charges the wrong provider depending on classpath ordering, and that kind of bug is nearly impossible to reproduce reliably. NoUniqueBeanDefinitionException fails loudly at startup instead of failing quietly in production, which is exactly the trade-off you want.

Cause #1: Multiple Implementations of an Interface

This is the textbook case. You’ve got two or more classes implementing the same interface, and you’re injecting the interface type without specifying which one.

public interface NotificationSender {
    void send(String to, String message);
}

@Service
public class EmailNotificationSender implements NotificationSender {
    public void send(String to, String message) {
        // send email
    }
}

@Service
public class SmsNotificationSender implements NotificationSender {
    public void send(String to, String message) {
        // send sms
    }
}

Injecting NotificationSender anywhere now throws NoUniqueBeanDefinitionException. You have three real options, and which one you choose depends on intent.

Option 1: @Primary for a sensible default

Use this when one implementation is clearly the “normal” case and others are exceptions.

@Service
@Primary
public class EmailNotificationSender implements NotificationSender {
    public void send(String to, String message) {
        // send email
    }
}

Any plain @Autowired NotificationSender injection now resolves to EmailNotificationSender automatically. @Primary is a tiebreaker, not an exclusion — the other bean still exists in the context and can be injected explicitly elsewhere.

Option 2: @Qualifier for explicit selection

Use this when the caller needs to be specific about which implementation it wants, and there’s no obvious “default.”

@Service
public class OrderService {
    private final NotificationSender sender;

    public OrderService(@Qualifier("smsNotificationSender") NotificationSender sender) {
        this.sender = sender;
    }
}

By default, the qualifier name is the bean name — which, for a @Service-annotated class, is the class name with a lowercase first letter. You can override it explicitly too:

@Service
@Qualifier("sms")
public class SmsNotificationSender implements NotificationSender { /* ... */ }
public OrderService(@Qualifier("sms") NotificationSender sender) { /* ... */ }

Option 3: Inject them all

Sometimes you don’t want one bean — you want all of them, so you can loop through and use each. Spring supports this natively, no qualifiers required:

@Service
public class NotificationDispatcher {
    private final List<NotificationSender> senders;

    public NotificationDispatcher(List<NotificationSender> senders) {
        this.senders = senders;
    }

    public void broadcast(String to, String message) {
        senders.forEach(sender -> sender.send(to, message));
    }
}

This works because Spring special-cases collection types during injection — it doesn’t try to resolve a single bean, it gathers every matching bean into the list. If you need to know which sender is which, inject a Map<String, NotificationSender> instead; the keys are the bean names.

Cause #2: Duplicate @Bean Definitions Across Configuration Classes

This one’s sneakier because there’s no interface in sight — it’s a plain class getting registered twice.

@Configuration
public class DataSourceConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder().build();
    }
}

@Configuration
public class HttpClientConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder()
            .setConnectTimeout(Duration.ofSeconds(5))
            .build();
    }
}

Both methods produce a RestTemplate bean. Spring registers them under different names (restTemplate from each class doesn’t collide by name here since method name is the same — Spring Boot will actually error at startup on the name collision, but if the method names differ, e.g. restTemplate() and httpClient(), you’ll get two distinct RestTemplate beans and NoUniqueBeanDefinitionException the moment something injects RestTemplate without a qualifier).

The fix — consolidate or qualify:

@Configuration
public class HttpClientConfig {
    @Bean
    @Qualifier("timeoutRestTemplate")
    public RestTemplate httpClient() {
        return new RestTemplateBuilder()
            .setConnectTimeout(Duration.ofSeconds(5))
            .build();
    }
}
public MyService(@Qualifier("timeoutRestTemplate") RestTemplate restTemplate) { /* ... */ }

Honestly, the better fix is usually to delete one of them. Duplicate bean definitions like this are often leftovers from a merge conflict or two developers independently adding similar configuration. Grep your codebase for the return type before adding a qualifier — you might not need two beans at all.

Cause #3: Auto-Configuration Conflicts With Your Own Bean

Spring Boot’s auto-configuration classes register plenty of beans behind the scenes. If a starter dependency already provides a bean of a type you’re also defining yourself, you can end up with two competing candidates without ever writing a duplicate on purpose.

// Your own bean, meant to override the default
@Configuration
public class ObjectMapperConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        return mapper;
    }
}

If another library on the classpath also auto-configures an ObjectMapper bean (some do, especially in multi-module setups pulling in unrelated starters), you now have two. This is the “auto-configuration conflict” case, and it’s harder to spot because the second bean isn’t in your source code at all.

Diagnosing it: run your app with the auto-configuration report enabled.

# application.properties
debug=true

Or, look at the NoUniqueBeanDefinitionException message itself — it lists the class file location for every candidate. If one of them points into a .jar instead of your own target/classes, you’ve found your auto-configured competitor.

The fix — exclude the auto-configuration, or mark yours @Primary:

@SpringBootApplication(exclude = { JacksonAutoConfiguration.class })
public class Application { /* ... */ }
@Bean
@Primary
public ObjectMapper objectMapper() {
    // your custom one always wins
}

@Primary is usually the safer choice — excluding an entire auto-configuration class can silently remove other beans you didn’t intend to lose.

Still Not Working?

A few edge cases that trip people up even after they think they’ve fixed it:

  • @Primary on the wrong bean, or on two beans at once. If you mark more than one candidate @Primary, you’re back to square one — Spring can’t break a tie between two primaries either.
  • @Qualifier name doesn’t match. Qualifier matching is exact-string, case-sensitive. A typo just produces NoSuchBeanDefinitionException instead, which at least points you in the right direction.
  • Test contexts pull in extra beans. @MockBean and @SpringBootTest can introduce additional candidates that don’t exist in your regular application context, so a class that starts fine in production can fail only in tests. Check your test configuration for stray @Bean methods or leftover mock beans from a shared test base class.
  • Generic types don’t disambiguate the way you’d expect. List<String> and List<Integer> look different to you, but at runtime Spring resolves both as List unless you use @Qualifier or ResolvableType-aware injection. If you have two List-returning @Bean methods with different generic parameters, you’ll still hit ambiguity.
  • Field injection hides the problem until runtime. If you’re using @Autowired on a field instead of a constructor parameter, the ambiguity error can surface later than you’d expect — sometimes only when that specific field actually gets accessed in a lazily-initialized bean. Constructor injection fails fast, at context startup, which is one more reason to prefer it over field injection in general.

Once you’ve applied a fix, write a quick context-loading test so this doesn’t regress silently the next time someone adds a third implementation:

@SpringBootTest
class ApplicationContextLoadsTest {

    @Autowired
    private NotificationSender sender;

    @Test
    void contextLoadsWithUnambiguousSender() {
        assertNotNull(sender);
    }
}

If a future teammate adds a third NotificationSender implementation without a qualifier, this test fails in CI instead of surprising someone in production logs.

Prevention Tips

A little discipline up front saves you this whole debugging cycle later:

  • Default to @Qualifier over @Primary when there’s no obvious default. @Primary is convenient, but it can mask the fact that a decision is being made implicitly. If two implementations are genuinely equal candidates, make the caller choose.
  • Keep @Configuration classes focused. One class, one area of concern. It’s much easier to spot a duplicate @Bean method when related beans live together instead of scattered across a dozen configuration classes with overlapping responsibilities.
  • Name your @Bean methods after what they return, not how they’re built. restTemplate() and internalRestTemplate() are easy to confuse; paymentServiceRestTemplate() and analyticsRestTemplate() make the intent obvious at the call site.
  • Run debug=true occasionally, even when things are working. The auto-configuration report shows you what’s being registered behind the scenes before it becomes a 2 a.m. production surprise.

Summary Checklist

  • [ ] Read the exception message — it names every competing bean and where it’s defined
  • [ ] Decide if you need one implementation (@Primary), a specific one (@Qualifier), or all of them (List<T> / Map<String, T>)
  • [ ] Check for duplicate @Bean methods across separate @Configuration classes
  • [ ] Check whether a starter dependency auto-configured a competing bean
  • [ ] Make sure only one bean is marked @Primary for a given type
  • [ ] Double-check @Qualifier values match exactly, including case

NoUniqueBeanDefinitionException is one of the more self-explanatory Spring exceptions — the message tells you exactly what collided — but the stack trace around it can get noisy in a large application context. Use Debugly’s trace formatter to quickly parse and analyze Java stack traces and pull out the bean names and file locations without scrolling through a wall of Spring startup logs.

If you’re also fighting missing beans instead of duplicate ones, check out our guide on Spring Boot NoSuchBeanDefinitionException, or if the problem is deeper in the dependency graph, our Spring Boot BeanCreationException guide.