Spring Boot · intermediate

Spring NoSuchBeanDefinitionException: trace the missing bean contract

Spring NoSuchBeanDefinitionException indicates the ApplicationContext could not resolve a required bean by type or qualifier during dependency injection. This guide maps the exception's nested detail message to concrete configuration boundaries—component scanning, explicit @Bean declarations, conditional annotations, and proxy generation—so engineers can trace the missing bean contract rather than guess at the cause.

The symptoms

  • Application startup fails with org.springframework.beans.factory.NoSuchBeanDefinitionException and a nested message naming either a type, a qualifier name, or a constructor parameter index that could not be satisfied.
  • ApplicationContext refresh halts before any CommandLineRunner or web server binds; logs show a BeanCreationException with the NoSuchBeanDefinitionException as the root cause rather than a child warning.
  • A single bean class fails to load while sibling beans of the same package succeed, indicating scope or annotation filtering rather than a wholesale scan failure.
  • Tests pass individually but fail under @SpringBootTest with a different active profile, suggesting profile-scoped bean visibility rather than a wiring bug.
  • Exception surfaces only when a @Lazy boundary is resolved or when an @Autowired Optional<T> is unwrapped, indicating a deferred injection path rather than eager startup wiring.

Likely causes

  • The target bean class resides outside the basePackages defined on @ComponentScan, @SpringBootApplication, or the XML equivalent, so component scanning never indexes it.
  • An annotation-based bean declaration is shadowed by an active @Conditional expression: @ConditionalOnProperty, @ConditionalOnClass, @ConditionalOnMissingBean, or @Profile evaluates to false under the current environment.
  • Two beans share the same type but no @Qualifier, @Primary, or unique bean name is supplied, so Spring cannot decide which candidate to inject and treats both as missing in some resolution paths.
  • The bean is declared in a configuration class that is itself excluded via @ComponentScan filters, @EnableAutoConfiguration excludes, or a too-narrow @SpringBootApplication(scanBasePackages=...).
  • A constructor with multiple parameters requires a registered ParameterNameDiscoverer, and the parameter name or @Qualifier does not match any registered candidate when compiled without -parameters.
  • The expected bean is produced by an auto-configuration that has been overridden or excluded via META-INF/spring.factories, AutoConfiguration.imports, or a user-supplied @Configuration with the same bean name.

First ten minutes

  1. 01Capture the full stack trace; locate the NoSuchBeanDefinitionException and read its message field, which names either the required type (interface/class), the qualifier, or the parameter index that failed to match.
  2. 02Open the source file at the line indicated by the stack trace and identify the injection site: field, constructor parameter, or method parameter that triggered resolution.
  3. 03Derive the expected bean contract from that injection site: required type, any @Qualifier value, expected bean name, and whether the injection is @Autowired(required=true), Optional, or @Lazy.
  4. 04Search the codebase for candidate producer classes: classes annotated with @Component, @Service, @Repository, @Controller, @Configuration + @Bean, and any @Conditional metadata that gates them.
  5. 05Compare the candidate class's fully qualified name against the @ComponentScan basePackages of the active @SpringBootApplication; a mismatch outside the scan root is the most common cause.
  6. 06If a candidate exists, evaluate each @Conditional expression against the active profile, property values, and classpath presence documented in application.properties, application.yml, or environment variables.

Evidence to collect

  • The exact NoSuchBeanDefinitionException message string, including any "expected at least X bean which qualifies as autowire candidate" or "no qualifying bean of type" prefix.
  • The injection site line number, target field or parameter type, and any @Qualifier or @Value annotation present at that site.
  • The fully qualified name of @SpringBootApplication and the resolved basePackages value, plus any @ComponentScan directives.
  • List of @Conditional annotations on candidate producer classes, paired with the property/class state they evaluate against at runtime.
  • Output of an ApplicationContext introspection step (such as the conditions evaluation report or actuator/beans endpoint in a non-production build) listing all beans of the expected type and their names.

Where to look

  • src/main/java tree for the component or @Configuration that should declare the missing bean, relative to the @SpringBootApplication root.
  • src/main/resources/application.properties and application.yml files for profile-scoped property keys referenced by @ConditionalOnProperty.
  • META-INF/spring.factories (legacy) and META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports for the auto-configuration class FQNs that should produce the bean.
  • ConditionalOnClass targets in the resolved classpath; a missing optional dependency silently disables an auto-configuration rather than throwing a different error.
  • Constructor parameter list of the injection target class; ensure the parameter types align with the produced bean types and that -parameters compile flag is set if parameter names are relied upon.

Diagnostic steps

  1. 01Reproduce the failure with the same active profile and property sources used in the failing environment; mismatched profiles are a frequent false-lead source.
  2. 02Confirm the candidate producer class is on the classpath using the project's compiled artifact listing or dependency tree, eliminating shaded or repackaged JAR issues.
  3. 03Inspect the spring-boot conditions evaluation report (debug=true) or the /actuator/beans endpoint in a non-production build to enumerate registered beans matching the expected type.
  4. 04Disable @Conditional evaluations one at a time (temporarily remove @Profile, @ConditionalOnProperty) to verify whether a condition is the gate; revert before commit.
  5. 05If multiple beans match, add @Primary to the intended candidate or supply a matching @Qualifier at the injection site to remove resolution ambiguity.
  6. 06If the bean is produced only by auto-configuration, verify the AutoConfiguration.imports file registers it under the current module and no user @Configuration defines a bean with the same name overriding it.
  7. 07If the injection site uses constructor injection without -parameters, recompile with -parameters or annotate parameters with @Qualifier/@Autowired to make resolution deterministic.

Common mistakes

  • Treating NoSuchBeanDefinitionException as a wiring typo and adding @Autowired blindly, when the underlying cause is a scan-root mismatch or a failed @Conditional gate.
  • Adding @Component on an inner class without marking it static, which Spring's component scanner does not register and yields a silent miss rather than a compile error.
  • Scattering @ComponentScan across modules without coordinating basePackages, producing one module whose beans are invisible to another's context.
  • Assuming @Profile("default") is active; an unset SPRING_PROFILES_ACTIVE and no default profile leaves no beans gated by @Profile registered.
  • Overriding an auto-configuration bean by declaring a same-named @Bean in user code, then debugging the auto-configuration as if it were missing.
  • Relying on field name autowire without @Qualifier when multiple candidates exist; Spring throws NoSuchBeanDefinitionException with "expected single matching bean but found N".

Safe fixes

  • Move the producer class into or under the @SpringBootApplication's package tree, or explicitly add its package to scanBasePackages when modular separation is required.
  • Adjust the active profile or supply the property expected by @ConditionalOnProperty so the gate evaluates true; verify with the conditions report before and after.
  • Introduce @Primary on the intended candidate, or annotate the injection site with @Qualifier("intendedBeanName") so resolution has a deterministic answer.
  • Annotate ambiguous constructor parameters with @Qualifier or recompile the module with the -parameters flag so parameter-name autowiring resolves reliably.
  • If an auto-configuration is silently disabled, add the missing optional dependency to the classpath or remove the @ConditionalOnClass gate in a local override only after documenting the deviation.
  • For nested configuration classes not picked up, annotate the enclosing @Configuration with @Import or move the @Bean method to a scanned configuration to make registration explicit.

Prove the fix

  1. 01Application startup completes past the BeanCreationException stage and binds the web server (or main()) with the previously failing injection site instantiated exactly once.
  2. 02The conditions evaluation report (debug=true) lists the formerly missing bean under the matching type with a positive evaluation outcome for every @Conditional on its producer.
  3. 03Where the resolution involved multiple candidates, the beans endpoint in a non-production build shows exactly one bean matching the injection site's required type and qualifier, and it is the intended candidate.
  4. 04Re-running the original failing test or replaying the original startup scenario no longer raises NoSuchBeanDefinitionException, and subsequent @SpringBootTest cases that exercise the same wiring pass consistently.

Prevention and next steps

  • Establish a single source of truth for the application root and document any cross-module @ComponentScan bases to prevent silent scan gaps during refactors.
  • Keep auto-configuration overrides, same-named @Bean declarations, and exclusion entries reviewed in code review so a missing bean never masks an unintended override.
  • Favor constructor injection with explicit @Qualifier annotations over field-name autowire so that resolution ambiguity becomes a compile-time concern rather than a startup failure.
  • Adopt the conditions evaluation report in CI debug builds to catch silent conditional gating before runtime, and gate the report behind a non-production flag.
  • Compile producer modules with -parameters or annotate parameters with @Qualifier so parameter-name autowiring remains deterministic across toolchain changes.

Safe commands and checks

java -jar <app-jar>.jar --debug=true > app-debug.log 2>&1; tail -n 200 app-debug.log | grep -i "Positive matches" -A 50 to enumerate beans of the expected type after resolution.
mvn -q -DincludeArtifactIds=<module-artifact> dependency:tree | grep -i <expected-producer-class-fqn> to confirm the candidate class is on the resolved classpath.
javap -p -c target/classes/com/example/producer/<Candidate>.class | head -n 40 to inspect compiled parameter names and verify whether -parameters retention is active.
mvn -q -DskipTests compile -Dmaven.compiler.parameters=true then re-run the failing test to confirm whether parameter-name retention resolves the injection.
unzip -p <app-jar>.jar BOOT-INF/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports to inspect registered auto-configurations in the packaged artifact.
grep -RIn "@ComponentScan\|@SpringBootApplication" src/main/java to map declared scan roots against the producer class's package and surface any boundary mismatch.