java.lang.NoSuchMethodError の修正:Javaの依存関係の競合を解決するためのガイド

intermediate Java2026-04-24| Java (全バージョン), Maven, Gradle, Windows, Linux, macOS

Error Message

Exception in thread "main" java.lang.NoSuchMethodError: com.example.utils.StringHelper.format(Ljava/lang/String;)Ljava/lang/String;
#java#nosuchmethoderror#依存関係の競合#maven#gradle

幽霊メソッドの謎

IDE上ではエラーはゼロ。ビルドも完璧にパスします。しかし、アプリケーションを起動した瞬間に、java.lang.NoSuchMethodErrorという壁にぶつかります。Javaの悪名高い「JAR地獄(JAR Hell)」と格闘したことがある人なら、このもどかしさをよく知っているはずです。

このクラッシュは、実行環境がコンパイル時に使用されたものとは異なるバージョンのクラスを見つけたときに発生します。コードは特定のメソッドシグネチャを期待していますが、JVMがロードしたJARでは、そのメソッドが欠落しているか、名前が変更されているか、あるいはパラメータが異なっています。これはロジックのバグではありません。クラスパスの設定ミスです。

Exception in thread "main" java.lang.NoSuchMethodError: com.example.MyClass.myMethod()V

この不一致の原因は何か?

犯人はほぼ間違いなく**推移的依存関係(transitive dependencies)**です。例えば、プロジェクトでLibrary-ALibrary-Bを使用しているとしましょう。どちらもGuavaに依存していますが、必要としているバージョンが異なります。Library-AはGuava 31.0を必要とし、Library-BはGuava 14.0を引き込みます。もしビルドツールがバージョン14.0を選択した場合、新しいバージョンに対してコンパイルされたコードは、10年前には存在しなかったメソッドを呼び出そうとしたときに失敗します。

実行時、JVMはクラスパス上で最初に見つけたバージョンのクラスのみをロードします。もし誤ったバージョンが選択されると、アプリは停止します。

ステップ1:依存関係の密林を可視化する

どのバージョンがリークの原因となっているかを推測してはいけません。どのライブラリが古いJARを紛れ込ませているかを確認するために、依存関係グラフ全体を可視化する必要があります。

Mavenユーザーの場合

ターミナルを開き、以下のコマンドを実行します。通常、完全なレポートの生成には10秒もかかりません。

mvn dependency:tree -Dverbose -Dincludes=com.google.guava:guava

-Dverboseフラグは不可欠です。これにより、Mavenが自動的に解決した隠れた競合が明らかになります。バージョン争いの敗者を見つけるには、omitted for conflict(競合により省略)と記された行を探してください。

Gradleユーザーの場合

dependenciesタスクを実行して、Gradleがどのようにグラフを解決したかを調査します。

./gradlew dependencies --configuration runtimeClasspath

->シンボルを探してください。これは、Gradleが強制的にアップグレードまたはダウングレードを行った箇所を示しています(例:1.0.0 -> 2.0.0)。

ステップ2:競合を排除する

侵入者を特定したら、秩序を取り戻すための主な方法が2つあります。

オプションA:特定のライブラリを除外する

もしLibrary-Bが誤ったバージョンのGuavaを持ち込んでいるのであれば、ビルドツールに対してそれを完全に無視するように指示します。これにより、プロジェクトは実際に必要なバージョンを強制的に使用するようになります。

Mavenの場合:

<dependency>
    <groupId>com.another.library</groupId>
    <artifactId>library-b</artifactId>
    <version>2.4.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Gradleの場合:

dependencies {
    implementation('com.another.library:library-b:2.4.0') {
        exclude group: 'com.google.guava', module: 'guava'
    }
}

オプションB:グローバルバージョンを強制する

もし5つや10つの異なるライブラリがすべて同じ依存関係を巡って争っている場合は、グローバルなルールを設定する方がクリーンです。

Mavenの場合(Dependency Management):

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.0.1-jre</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Gradleの場合(Resolution Strategy):

configurations.all {
    resolutionStrategy {
        force 'com.google.guava:guava:31.0.1-jre'
    }
}

ステップ3:解決策を検証する

ビルドが完了したからといって、修正が成功したと思い込まないでください。デプロイ前に最終的なアーティファクトを検証しましょう。

- **ツリーを更新する:** 依存関係コマンドを再度実行し、不要なバージョンが消えていることを確認します。
- **JARの中身を確認する:** 「ファットJAR(fat JAR)」を使用している場合は、解凍してください。`javap`を使用して、コンパイルされたクラスにメソッドが存在することを確認します。
# 'format' メソッドが最終的なパッケージに実際に存在するか確認する
javap -cp my-app-all.jar com.example.utils.StringHelper | grep format

クリーンなクラスパスを維持するための予防策

- **BOM(Bill of Materials)を採用する:** Spring BootやAWS SDKなどのフレームワークはBOMを提供しています。これは、すべての内部コンポーネントのバージョンの互換性を保証する「信頼できる情報源」として機能します。
- **'Provided' スコープに注意する:** Tomcatのようなコンテナにデプロイする場合、ローカルのライブラリバージョンは実行時にサーバーが提供するものと一致している必要があります。
- **シャドウイング(Shadowing)をチェックする:** ShadeプラグインやShadowプラグインが、誤って同じクラスの2つの異なるバージョンを異なる名前でバンドルしていないか確認してください。

依存関係の競合は、Java開発における標準的な障害です。dependency:treeと除外(exclusion)をマスターすることで、4時間のデバッグの悪夢を5分の修正に変えることができます。

Related Error Notes