Home Java Spring NoSuchBeanDefinition — Bean Never Registered
Intermediate 5 min · September 23, 2026

Spring NoSuchBeanDefinition — Bean Never Registered

Spring NoSuchBeanDefinitionException: bean never registered.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.

Follow
Production
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
Before you start⏱ 12 min
  • Spring Boot basics
  • Dependency injection concepts
  • Reading startup logs
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • NoSuchBeanDefinitionException means the container has no bean of the required type — the class exists in code but was never registered
  • Top causes: missing @Component/@Service annotation, class outside scanned packages, @Bean name mismatch, or absent starter
  • Default scanning covers only packages below the application class — sibling packages are invisible
  • Multiple candidates need @Qualifier at the injection point or @Primary on the default bean
  • The condition evaluation report lists every candidate and exclusion — read it before changing code
✦ Definition~90s read
What is Spring NoSuchBeanDefinition Fix?

NoSuchBeanDefinitionException (commonly NoUniqueBeanDefinitionException for the multi-candidate variant) is Spring's error when dependency injection names a bean the container does not hold. The message states the required type, any required name or qualifier, and — for ambiguity — the candidates found.

Think of Spring's container as a company directory.

It fires at context startup for constructor injection, which is why whole applications refuse to boot over one missing registration.

Registration has exactly three doors: stereotype annotations discovered by component scanning, @Bean factory methods in configuration classes, and auto-configuration classes gated by conditions. Scanning additionally requires package coverage — the class must live under a scanned root.

Every incident traces to one door closed: no annotation, unscanned package, mismatched name, ambiguous candidates, or an unmet auto-configuration condition.

Beginners confuse this with classpath errors because both complain something is missing. The difference: the class loads fine (imports resolve, IDE navigates), but the container holds no definition for it. Code truth versus container truth is the defining split — and the fix always edits registration metadata, never business logic.

The professional stance makes registration visible and tested: stereotypes on every injectable, application class at the package root, explicit bean names and qualifiers, condition reports consulted before guessing, and module context tests booting in CI. Container membership becomes a proven property of the build rather than a hope exercised at deploy time.

Plain-English First

Think of Spring's container as a company directory. Writing a class is like hiring a person; registering a bean is adding them to the directory. NoSuchBeanDefinitionException means someone asked the directory for BillingService and got no listing — the person exists in the building (the code compiles) but was never added to the directory (no annotation, wrong department, wrong name spelling). The fix is never hiring twice; it is correcting the directory entry.

The application refuses to start: No qualifying bean of type com.example.BillingService available. The class exists — you wrote it yesterday, it compiles, the IDE autocompletes it. And yet Spring insists there is no such bean, with startup halted and a wall of condition-report text.

This exception is Spring's way of saying the container looked for a bean and found nothing matching. Registration is explicit in Spring: a class becomes a bean through a stereotype annotation, an @Bean method, or auto-configuration — and only if component scanning actually visits its package. Miss any link and the type exists in code but not in the container.

The frustration comes from the gap between code truth and container truth. The IDE sees classes; Spring sees registered definitions. Developers debug the class (correct) while the fix lives in the registration (annotation, package, name) — a different file, sometimes a different module.

This article closes that gap: how scanning, stereotypes, @Bean names, qualifiers, and auto-configuration conditions decide what exists. You will learn to read the expected-bean message, wield the condition report, and structure packages so this error stops appearing.

Missing Stereotypes: Annotated for Humans, Invisible to Spring

A class becomes a Spring bean through registration: a stereotype annotation (@Component and its specializations @Service, @Repository, @Controller), an @Bean factory method, or framework auto-configuration. Without one of these, the class is invisible to the container no matter how correct it is — compilation and container membership are independent facts.

The specializations carry identical scanning behavior with added semantics. @Service marks business logic, @Controller marks web handlers, and @Repository adds persistence-exception translation for data access. Choosing the honest stereotype documents the layer while registering the bean — one annotation doing two jobs.

The failure mode is silence: an unannotated class produces no warning at startup, only the downstream NoSuchBeanDefinitionException at the injection point. Nothing points back at the missing annotation except the zero-candidate listing. That indirection is why missing stereotypes survive code review — reviewers verify logic, not container metadata.

The prevention is a review rule with teeth: every injectable class carries a visible stereotype, and context-load tests prove registration in CI. A test that boots the module context fails on a missing annotation in seconds, converting a deploy-night mystery into a pull-request comment.

BillingService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.stereotype.Service;

@Service
public class BillingService {
    public String charge(String account, long cents) {
        if (account == null || account.isBlank()) {
            throw new IllegalArgumentException("account is required");
        }
        return "charged:" + cents;
    }
}
📊 Production Insight
A missing @Service survived review because the logic was flawless — only container metadata was absent. Rule: review checklists must verify stereotype annotations, not just logic.
🎯 Key Takeaway
No stereotype means no bean. Annotate every injectable and prove registration with context-load tests.

Scan Roots: Why Sibling Packages Are Invisible

Component scanning starts from a root: by default, the package of the application class. Every sub-package is visited; sibling packages are not. A service moved from com.example.billing to com.shared.billing crosses an invisible wall — annotated, compiled, and unscanned.

The application class placement is therefore architecture, not housekeeping. Teams that nest it two levels deep for tidiness shrink their scan root with every level, and each future package move gambles on invisible coverage. The safest layout puts the application class at the package root so the default scan covers the entire codebase by construction.

Explicit scanBasePackages documents intent when the layout cannot be flat. Multi-module projects with shared libraries name each root deliberately, turning invisible defaults into reviewed configuration. The attribute costs one line and ends all package-roulette incidents.

Verify coverage mechanically. The condition report lists scanned candidates; a missing class there means the scanner never visited it. When a bean vanishes after a move, the report — not the annotations — is the first document to open. Pair the rule with ArchUnit-style tests that fail builds on uninjected concrete dependencies.

ShopApplication.javaJAVA
1
2
3
4
5
6
7
8
9
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.example")
public class ShopApplication {
    public static void main(String[] args) {
        org.springframework.boot.SpringApplication.run(ShopApplication.class, args);
    }
}
📊 Production Insight
A sibling-package move halved a fleet for 40 minutes while annotations were rechecked three times. Rule: map scan coverage before approving any package move.
🎯 Key Takeaway
Default scanning covers sub-packages of the app class only. Keep the app class at the root or declare scanBasePackages.

@Bean Names: Method Names Are Bean Names

@Bean methods register under their method name by default: paymentGateway() above creates a bean named paymentGateway. Injection by name — @Qualifier("paymentGateway") or @Resource — must spell that exact name, and constructor parameters in recent Spring versions also bind by parameter name. One rename breaks all three silently.

The mismatch arrives through ordinary refactors. Renaming the method for readability, splitting one bean into two, or copying a configuration without updating injection points all produce zero-candidate failures naming a bean that looks like it should exist. It does exist — under its old name.

Explicit naming removes the coupling between method readability and bean identity: @Bean("payments") fixes the name regardless of method renames. Qualified injection points then bind to a stable contract instead of a Java identifier that style guides may rewrite.

When the message names an expected bean, search configurations for that exact string before theorizing. The bean definition is a line of code with a name; matching strings beats architectural speculation every time. Sketch the scan tree in onboarding docs so new packages land in covered ground, and review every application-class move as an architecture change.

ClientConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ClientConfig {
    @Bean
    public PaymentGateway paymentGateway() {
        return new PaymentGateway("https://pay.example.com");
    }
}
📊 Production Insight
A readability rename of a @Bean method broke three injection points with zero-candidate errors. Rule: @Bean("stable-name") decouples bean identity from Java identifiers.
🎯 Key Takeaway
@Bean names default to method names — renames break injection silently. Name beans explicitly and match strings exactly.

Two Candidates, One Point: @Qualifier and @Primary

When two beans share a type — Stripe and PayPal gateways, primary and replica data sources — type-only injection cannot choose. Spring fails rather than guesses, listing both candidates in an expected-single-but-found-two message. The failure is the framework refusing to make your decision.

@Qualifier decides at the injection point by naming the wanted bean. It keeps both candidates registered and selects per consumer: checkout takes Stripe, refunds take PayPal, each explicit. Qualifier values are strings, so constants beat literals for typo resistance.

@Primary decides globally by marking the default candidate. Unqualified injection points receive the primary; qualified points still get their named bean. One primary per type keeps the default unambiguous — two primaries of one type fail as loudly as two unmarked candidates.

Prefer constructor injection throughout, because its failure messages list every candidate with types and names at startup. Field injection fails at the same point with less context, and setter injection defers some failures to first use. Fail-fast clarity is worth the extra constructor lines. Record bean names in module READMEs where cross-team injection depends on them.

CheckoutService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class CheckoutService {
    private final PaymentGateway gateway;

    public CheckoutService(@Qualifier("stripeGateway") PaymentGateway gateway) {
        this.gateway = gateway;
    }
}
📊 Production Insight
Adding a second gateway flipped startup from working to ambiguous-candidate failures. Rule: every multi-implementation type gets @Primary plus @Qualifier constants from day one.
🎯 Key Takeaway
@Qualifier selects per injection point; @Primary sets the global default. Constructor injection reports both with full context.

Auto-Configuration Conditions: Silent Gates on Framework Beans

Auto-configuration creates framework beans — DataSources, templates, mappers — when conditions hold: a class on the classpath, a property set, another bean absent. Each condition is a silent gate, and a changed dependency or property flips gates without touching application code.

The condition evaluation report is the map of those gates. Rerun with debug enabled and search for the auto-configuration class: the report shows which @ConditionalOnClass found its class, which @ConditionalOnProperty matched, and which @ConditionalOnMissingBean deferred. The exclusion reason is printed plainly — no inference needed.

Missing starters are the common trigger. Upgrading or trimming dependencies removes the class an auto-configuration waited on, and its beans vanish together. The error names infrastructure types rather than application code, which sends teams searching their own classes for a dependency problem.

Property gates deserve equal suspicion. Management endpoints, security filters, and data features toggle on properties; a renamed or deleted property silently unregisters whole bean families. Diff properties alongside dependencies in every upgrade review. Standardize qualifier constants in a shared class to eliminate string drift.

📊 Production Insight
A trimmed dependency removed four auto-configured beans with infrastructure-type errors. Rule: diff dependencies and properties together in upgrades — conditions couple them invisibly.
🎯 Key Takeaway
Starter and property conditions gate framework beans silently. Read the condition report's exclusion reasons before touching code.

Slice Tests and Module Context Tests That Prevent It

Test slices (@WebMvcTest, @DataJpaTest) load partial contexts for speed, and partial contexts exclude beans by design. A service injected in production but absent in the slice fails with the same exception as a genuinely missing bean — same message, opposite meaning.

The fix matches the intent: @Import the needed configuration into the slice, mock the boundary with @MockBean, or test against the full context when the integration is the point. Each option declares what the test covers instead of pretending the slice is the app.

Module context tests are the systematic prevention. A @SpringBootTest per module that merely boots its context catches missing registrations, broken scans, and condition surprises in CI within seconds. They are the cheapest tests with the highest registration value.

Keep a simple rule for the team: a bean used in production must appear in at least one booting test. Untested registrations are unregistered until proven otherwise — the container's opinion at deploy time is the only one that counts. Version the condition report diff inside upgrade pull requests so reviewers see gate changes, and keep a starter-to-bean map for key dependencies so missing starters point at exact artifacts.

🔥Slice Tests Exclude Beans on Purpose
Slice tests load partial contexts by design — a missing bean there means the slice excludes it, not that the app is broken. Import it or test the owning module.
📊 Production Insight
Zero module context tests let a package move reach production uncaught. Rule: every module boots its context in CI — registration without a booting test is a rumor.
🎯 Key Takeaway
Slice exclusions are intentional — import or mock deliberately. Boot every module's context in CI to prove registration.
● Production incidentPOST-MORTEMseverity: high

A Package Move Deleted a Bean and Halved the Fleet for 40 Minutes

Symptom
Half the fleet failed startup immediately after the deploy while the other half served normally. Health checks never passed on new pods, the deploy auto-paused, and billing traffic ran on 3 overloaded survivors at 89% CPU for 40 minutes.
Assumption
The new package was deemed internal reorganization with no behavior change, so it skipped context-load tests (the module had none). The application class sat two levels deep already, and nobody mapped which packages the default scan actually covered. Reviewers approved a pure move with no test evidence.
Root cause
A refactoring moved BillingService from com.example.billing to com.shared.billing — a sibling of the application class's com.example.app root — taking it outside default component scanning. All 6 pods failed startup with No qualifying bean of type BillingService, and the deploy halted at 50%. The class was annotated correctly and compiled fine; it was simply never visited by the scanner. Diagnosis took 25 minutes because the team rechecked annotations three times before mapping scan coverage.
Fix
The package was moved back under the scan root within 40 minutes, restoring all 6 pods. The permanent fix relocated the application class to the package root, added scanBasePackages explicitly as documentation, and introduced context-load tests for every module in CI. Package moves now require a green context test before merge.
Key lesson
  • Package moves are behavior changes in Spring — default scanning makes directory layout a runtime contract, not cosmetics.
  • Every module needs a context-load test; untested modules hide registration breaks until deploy.
  • Keep the application class at the package root so the default scan covers everything by construction.
Production debug guideFive checks that trace the expected bean from message to registration.5 entries
Symptom · 01
No qualifying bean at startup with no obvious cause
Fix
Rerun with --debug or debug=true and search the condition evaluation report for the expected type. The report lists every candidate bean of that type plus exclusions — zero candidates means registration never happened; start with annotations and packages.
Symptom · 02
Suspecting a missing annotation or wrong package
Fix
Run grep -rn "@Service\|@Component\|@Repository\|@Bean" on the expected class's file. A missing annotation is the top cause. Then confirm the class package sits under the application class package — siblings are unscanned by default.
Symptom · 03
@Bean name mismatch suspected
Fix
Compare the @Bean method name in the @Configuration class against the name in the error message and any @Qualifier value. Rename the method or add the qualifier so all three agree exactly, including case.
Symptom · 04
Found two beans where one was expected
Fix
Read the full message: expected single matching bean but found 2 names the candidates. Add @Qualifier("exactName") at the injection point for the wanted one, or @Primary on the default — then rerun to confirm a single match.
Symptom · 05
Framework bean missing after dependency changes
Fix
Search the condition report for the auto-configuration class (e.g. DataSourceAutoConfiguration) and read which @ConditionalOnClass or @ConditionalOnProperty excluded it. Add the missing starter dependency or set the enabling property.
NoSuchBeanDefinitionException Causes Compared
Root CauseHow to ConfirmFixPrevention
Missing stereotype annotationClass has no @Component/@Service; condition report shows no candidateAdd the stereotype or an @Bean methodReview rule: every injectable carries a stereotype
Class outside component-scan packagesAnnotated but condition report never lists itMove under the scan root or extend base packagesKeep app class at package root; test slice coverage
@Bean method name mismatchMessage expects name X; config method is named YAlign the name, or inject with @QualifierName beans deliberately; assert context in tests
Multiple candidates, no qualifierMessage lists two beans where one was expectedAdd @Qualifier or @PrimaryQualify every injection point with siblings
Missing starter or disabled auto-configCondition report shows auto-config excluded by @ConditionalAdd the starter; check properties excluding itBoot animals: actuator conditions endpoint in staging
⚙ Quick Reference
4 commands from this guide
FileCommand / CodePurpose
BillingService.java@ServiceMissing Stereotypes
ShopApplication.java@SpringBootApplication(scanBasePackages = "com.example")Scan Roots
ClientConfig.java@Configuration@Bean Names
CheckoutService.java@ServiceTwo Candidates, One Point

Key takeaways

1
No qualifying bean means the type was never registered
check annotation, package, and name before anything else.
2
Default scanning covers only packages below the application class; siblings are invisible.
3
@Bean names default to method names
inject the exact name or qualify explicitly.
4
Multiple candidates need @Qualifier at the point or @Primary on the default.
5
The condition report shows every candidate and exclusion
read it before guessing.
6
Add context-load tests per module so missing beans fail in CI, not at deploy.

Common mistakes to avoid

5 patterns
×

Forgetting the stereotype annotation entirely

Symptom
Plain class with no annotation is invisible to scanning. The error names the type and lists zero candidates, while the class sits in the right package looking innocent.
Fix
Add the missing stereotype (@Component, @Service, @Repository) or declare an @Bean method, and confirm the package sits under the component-scan root. Rerun with the condition report to watch the bean appear.
×

Placing the bean outside the scanned packages

Symptom
Annotated correctly but in a sibling package the scan never visits. Works when the app class moves, breaks when it returns — package layout roulette.
Fix
Move the class under the scanned root or extend the scan base packages explicitly. Keep the application class at the package root so the default scan covers everything below it.
×

Injecting by a name that matches no @Bean method

Symptom
Configuration declares userService() but code asks for service. @Bean names default to method names, and the mismatch fails with zero candidates under a similar name.
Fix
Inject by the @Bean method name, add @Qualifier at the injection point, or mark one candidate @Primary. Read the expected-bean listing in the message to match names exactly.
×

Two beans of one type with no qualifier

Symptom
Error flips from no qualifying bean to expected single matching bean but found two. Scanning found too much instead of too little, and the injection point must choose.
Fix
Disambiguate with @Qualifier naming the wanted bean, or @Primary on the default. Constructor injection lists every candidate in the message — pick explicitly instead of hoping.
×

Missing the starter that auto-creates the bean

Symptom
Framework types like DataSource or RestTemplate have no bean because the starter is absent. The error names infrastructure, not application code, which misdirects the search.
Fix
Add the starter or auto-configuration dependency, or declare the bean manually. Condition reports show the auto-config class and exactly which @ConditionalOnClass or property excluded it.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does NoSuchBeanDefinitionException mean?
Q02SENIOR
How do scanned packages cause this error?
Q03SENIOR
Why does a @Bean method name mismatch fail?
Q04SENIOR
Two candidates, one injection point — @Qualifier or @Primary?
Q05SENIOR
Walk through a systematic missing-bean diagnosis.
Q01 of 05JUNIOR

What does NoSuchBeanDefinitionException mean?

ANSWER
It means the container has no bean matching the required type (and name): the class lacks a stereotype, sits outside scanned packages, or the @Bean name mismatches. You read the expected-bean listing in the message and check annotations plus scan roots.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Do @Service and @Repository also register beans?
02
Which packages does component scanning cover?
03
What does @Primary actually do?
04
Can slice tests cause this error?
05
How do I see why a bean is missing?
06
Constructor versus field injection for diagnosis?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Written from production experience, not tutorials.

Follow
Verified
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
🔥

That's Spring. Mark it forged?

5 min read · try the examples if you haven't

Previous
Java UnsupportedClassVersion Fix
1 / 3 · Spring
Next
Spring BeanCreationException Fix