はじめに (対象読者・この記事でわかること)

Spring BootでWebアプリを開発していると、突然NoSuchBeanDefinitionExceptionがスローされて起動に失敗する場面に遭遇します。
本記事は、「Springのエラー原因がわからず、Web検索しても同じ状況の事例が見つからない」中級者〜上級者の方を対象にしています。
読み進めることで、以下のことが身につきます。

  • 例外メッセージの「required a bean of type 'XXX' that could not be found」を正確に読み解く方法
  • パッケージ構成、コンポーネントスキャン、アノテーション設定のどこに落とし穴があるかをすぐに切り分けるコツ
  • 実際のプロジェクトで遭遇した3つの事例と、それぞれの解決コード

前提としてJava 17以降とSpring Boot 3系の知識、Gradle/Mavenの基本操作は理解済みであることを想定しています。

前提知識

  • Java 17以上の文法(record、varなど)
  • Spring Frameworkにおける@ComponentScan@Configuration@Beanの役割
  • IDE(IntelliJ IDEA / Eclipse)でデバッガを止めて変数の中身を見る操作

そもそもNoSuchBeanDefinitionExceptionとは

SpringのDIコンテナは、アプリケーション起動時に必要なBeanをすべて作成できなければ初期化を完了しません。
「指定された型・名称のBeanが見当たらない」場合にスローされるのがNoSuchBeanDefinitionExceptionです。
原因は大きく分けて以下の3パターンに集約されます。

  1. コンポーネントスキャンの範囲が届いていない
  2. 条件付きBean(@ConditionalOn~)が意図せず無効化されている
  3. 複数の実装が存在し、優先度の指定ミスで該当Beanが選択されない

エラーログを見た瞬間に「1か2か3か」を見極められると、解決までの時間が短縮できます。

実践:3つのハマりどころと切り分け手順

ここでは、私が先月経験した3つのケースを再現しながら、切り分け→特定→修正までを全て公開します。

ケース1:パッケージを移動したら突然Beanが見つからなくなった

事象

マルチモジュール構成でdomainモジュールをinfrastructureモジュールに統合。
その直後に起動時に以下のエラーが出現。

Parameter 0 of constructor in com.example.app.service.OrderService required a bean of type 'com.example.domain.repository.OrderRepository' that could not be found.

切り分け手順

  1. @SpringBootApplicationが置かれているクラス(以下App.java)を開く
  2. デフォルトの@ComponentScanApp.javaと同一パッケージ配下のみをスキャンすることを確認
  3. 統合後、OrderRepositorycom.example.infrastructure.repositoryに移動しているため、スキャン対象外になっていることが判明

解決コード

Java
// App.java(起点クラス) package com.example.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication //@SpringBootApplication already includes @ComponentScan only under com.example.app public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } // 修正①:スキャン対象を広げる @SpringBootApplication(scanBasePackages = {"com.example.app", "com.example.infrastructure"})

あるいはモジュール構成を見直して、domainパッケージを残してinfrastructure側で@Repositoryを付け直す方法もあります。

ケース2:@ConditionalOnPropertyでBeanが無効化されていた

事象

ステージング環境では動作していたアプリを本番環境にデプロイしたところ、同例外が発生。
該当BeanはFeatureToggleConfigという独自クラス。

切り分け手順

  1. application-staging.ymlapplication-prod.ymlの差分を比較
  2. 該当Bean生成メソッドに@ConditionalOnProperty(name = "feature.toggle.enabled", havingValue = "true")が付与されていることを確認
  3. 本番環境の設定ファイルにfeature.toggle.enabled=falseが設定されており、Beanが条件付きで無効化されていることが判明

解決コード

Java
@Configuration public class FeatureToggleConfig { @Bean @ConditionalOnProperty(name = "feature.toggle.enabled", havingValue = "true", matchIfMissing = true) // matchIfMissingを追加 public FeatureService featureService() { return new FeatureServiceImpl(); } }

matchIfMissing = trueを付けることで、設定漏れ時に安全側(Bean生成)に倒します。
本番で明示的に無効化したい場合はfalseを追記すれば即座に反映されます。

ケース3:同一インタフェースの実装が複数存在し、Primaryを指定し忘れた

事象

NotificationSenderインタフェースを2つ実装(EmailSenderLineSender)していた。
どちらも@Componentを付けていたが、注入先で@Autowired NotificationSender senderとしていたところ例外発生。

切り分け手順

  1. エラーメッセージを読むと「No qualifying bean of type 'NotificationSender' available: expected single matching bean but found 2」に変更されていることに注目
  2. 複数Bean該当パターン(3番)と即判別
  3. @Primaryまたは@Qualifierのどちらかで解決可能

解決コード

Java
@Component @Primary // デフォルト実装を明示 public class EmailSender implements NotificationSender { } @Component public class LineSender implements NotificationSender { } // 注入側 @Service public class NotificationService { private final NotificationSender sender; // EmailSenderがPrimaryで注入される public NotificationService(NotificationSender sender) { this.sender = sender; } }

もし特定のBeanを明示的に使いたい場合は@Qualifier("lineSender")を付与します。

ハマった点・エラー解決まとめ

  • マルチモジュールでパッケージ移動後は必ず@ComponentScanの範囲を確認
  • 環境差分で突然Beanが消えた場合は@Conditional~系を疑う
  • インタフェースの実装が複数ある際は@Primary/@Qualifierを使って衝突を回避

デバッグTips

  1. Spring Boot 3.x以降は--debugオプションで起動するとConditionEvaluationReportが標準出力される(どのBeanがスキップされたか一覧が見られる)
  2. IntelliJ IDEAのSpringプラグインを使えば、Beanの依存図を可視化できる
  3. ログレベルをorg.springframework.boot.autoconfigure=DEBUGにすると@ConditionalOn~の評価結果が詳細に出力される

まとめ

本記事では、NoSuchBeanDefinitionExceptionが発生した際の「3パターンの切り分け」と、実プロジェクトで遭遇した3つのケースを再現しながら解決までを解説しました。

  • パッケース構成変更後のスキャン漏れに注意
  • @ConditionalOn~で環境差分が意図せずBeanを無効化する可能性
  • 複数実装がある場合は@Primary/@Qualifierで衝突を回避

これらのポイントを抑えることで、エラー発生時も即座に原因を絞り、修正までのリードタイムを短縮できます。
次回は「Spring Boot 3.2で導入された@RegisterReflectionとNative Image対応」について掘り下げていきます。

参考資料