アイキャッチ

実装・エラー解決

Spring BootでBeanCreationExceptionが出たら|Caused byと失敗したBeanを確認

BeanCreationExceptionで最初に確認すること

起動ログの先頭に invoiceJob が出ても、直すのがそのBeanとは限りません。請求バッチの例では、invoiceJob が使う invoiceRules は登録済みですが、その初期化処理が設定値 0 を拒否します。ログから、依存側のBean・実際に失敗したBean・最深の原因を分けます。

関連記事Javaのスタックトレースの読み方|Caused byの後、どのコードを開けばいい? Caused byと自作コードの行を手掛かりに、例外の原因・渡された値・次に調べる場所を絞る手順を確認できます。 記事を読む

実行用サンプル

Bean生成時に初期化が失敗するケースと、同じBean定義のまま設定値を直すケースをJUnitで確認できます。Spring BootやWebサーバーは起動しません。

JDK 17、Maven 3.9系、Spring Framework 6.2.19。ZIPにはソース全文、pom.xml、READMEを収録しています。展開先で mvn test を実行します。

要約

まず先頭から「起動の途中でどのBeanが作れなくなったか」を控えます。次に Caused by を最後までたどり、例外名と入力値を拾います。今回開くのは invoiceRules の初期化処理と invoice.batch-size の設定です。

エラー例

invoiceJob の生成時に invoiceRules を受け取ります。Springが invoiceRules を作って initialize() を呼ぶと、値が 0 のため例外が出ます。次のログはJUnitの実行結果から4行を抜き出したものです。

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'invoiceJob' defined in example.BeanCreationCauseTest$Config: Unsatisfied dependency expressed through method 'invoiceJob' parameter 0: Error creating bean with name 'invoiceRules' defined in example.BeanCreationCauseTest$Config: invoice.batch-size must be positive: 0
[…]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'invoiceRules' defined in example.BeanCreationCauseTest$Config: invoice.batch-size must be positive: 0
[…]
Caused by: java.lang.IllegalArgumentException: invoice.batch-size must be positive: 0
[…]
	at example.BeanCreationCauseTest$InvoiceRules.initialize(BeanCreationCauseTest.java:66)

BeanCreationCauseTest.java の全文です。

package example;

import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.MapPropertySource;

import static org.junit.jupiter.api.Assertions.*;

class BeanCreationCauseTest {
    @Test
    void findsTheFailedBeanAndInvalidValue() {
        try (var context = createContext(0)) {
            var outer = assertThrows(UnsatisfiedDependencyException.class, context::refresh);
            assertEquals("invoiceJob", outer.getBeanName());

            var failed = assertInstanceOf(BeanCreationException.class, outer.getCause());
            assertEquals("invoiceRules", failed.getBeanName());

            var root = assertInstanceOf(IllegalArgumentException.class, failed.getCause());
            assertEquals("invoice.batch-size must be positive: 0", root.getMessage());
            assertEquals("initialize", root.getStackTrace()[0].getMethodName());
            outer.printStackTrace(System.out);
        }
    }

    @Test
    void startsWhenTheSamePropertyHasAValidValue() {
        try (var context = createContext(20)) {
            context.refresh();
            assertEquals(20, context.getBean(InvoiceJob.class).rules().batchSize());
        }
    }

    private AnnotationConfigApplicationContext createContext(int batchSize) {
        var context = new AnnotationConfigApplicationContext();
        context.getEnvironment().getPropertySources().addFirst(
                new MapPropertySource("example", Map.of("invoice.batch-size", batchSize)));
        context.register(Config.class);
        return context;
    }

    @Configuration(proxyBeanMethods = false)
    static class Config {
        @Bean
        InvoiceJob invoiceJob(InvoiceRules rules) {
            return new InvoiceJob(rules);
        }

        @Bean(initMethod = "initialize")
        InvoiceRules invoiceRules(@Value("${invoice.batch-size}") int batchSize) {
            return new InvoiceRules(batchSize);
        }
    }

    record InvoiceJob(InvoiceRules rules) {}

    record InvoiceRules(int batchSize) {
        public void initialize() {
            if (batchSize < 1) {
                throw new IllegalArgumentException(
                        "invoice.batch-size must be positive: " + batchSize);
            }
        }
    }
}

修正例

このバッチは1件以上をまとめて処理する仕様なので、invoice.batch-size を 0 から 20 に直します。サンプルの成功ケースは設定値だけを変更し、コンテキストが起動し、invoiceJob から参照した値が20であることを確認します。JUnitは両ケースを含む2件です。

実環境では、先に対象環境で有効なプロファイルを確認します。設定ファイルと環境変数など複数箇所に値がある場合は、どの設定から 0 が採用されたかまで特定します。入力検証を外して起動だけ通すと、処理件数の誤りが後の実行まで残ります。

詳細

なぜそうなるのか?

SpringはBeanを生成した後、設定された初期化メソッドを呼びます。このサンプルでは @Bean(initMethod = "initialize") を使います。初期化が例外で終わると、対象のBeanと、それに依存するBeanの生成も失敗します。SpringのBean初期化コールバック

仕組みのポイント

最深の例外が候補0件を示す場合は、その時点で登録経路を調べます。初期化メソッドの例外なら、先にその自作コードと入力を見ます。調査を引き継ぐときは「invoiceJob → invoiceRules → initialize() で設定値0を拒否」と、名前・処理・値を一緒に残します。

関連記事Spring Bootの「No qualifying bean of type」原因|DIできないときの対処法 候補0件と読めたら、要求型とSpring BootのBean登録条件を確かめます。 記事を読む 関連記事NoSuchBeanDefinitionExceptionの原因|XML・@Serviceがあるのに登録されないとき 旧XMLを併用する場合に、XMLの読み込み元と探索範囲を確認します。 記事を読む

-実装・エラー解決
-,