Java NoClassDefFoundError の原因と解決方法【ClassNotFoundExceptionとの違いも解説】

NoClassDefFoundErrorとは?ざっくりと3行で
  • NoClassDefFoundErrorは、コンパイル時には存在したクラスの定義が実行時のクラスパス上に見つからないときに発生するエラーだ。
  • 実務では実行時クラスパスにコンパイル時のJARが含まれていないケースが大半で、ビルド環境と実行環境の差が原因になりやすい。
  • まずは実際に使われているクラスパスと、依存JARの同梱状況を確認すればいい。
NoClassDefFoundErrorが本番デプロイ直後に発生し、JAR同梱漏れに気づいて解決する4コマ漫画
①本番デプロイ直後にNoClassDefFoundErrorが発生し青ざめる。②デプロイ太郎がローカルと本番のJAR配置の違いを指摘する。③依存JARを同梱してビルドし直し無事起動する。④ビルド成果物を実機で確認する習慣が身につく。

今回描いたのは、ローカルでは正常に動いていたアプリケーションが、本番サーバーへのデプロイ直後だけNoClassDefFoundErrorで落ちてしまうという、実務でも頻発するシナリオです。原因の多くは、開発環境のIDEがプロジェクト内の依存関係を自動的にクラスパスへ含めてしまうため、ビルド成果物に本来含まれるべきJARが同梱されていないことに気づきにくい点にあります。

本番だけで再現するエラーは調査に時間がかかりがちですが、Caused by以下に表示されるクラス名を手がかりに、そのクラスがどのライブラリに含まれるかを特定すれば原因の特定は難しくありません。今回の4コマのように依存JARの同梱漏れが原因であれば、ビルド設定のスコープ指定やパッケージング手順を見直すことで解決できます。

放置すると本番リリースのたびに同じ調査を繰り返すことになりかねません。ビルドした成果物そのものを起動して動作確認するスモークテストを開発フローに組み込んでおけば、同様のトラブルを事前に防げるようになります。

NoClassDefFoundErrorの基本情報

NoClassDefFoundErrorは、Javaプログラムの実行中にJVMやクラスローダーがクラスの定義を読み込もうとして失敗したときに発生するエラーです。Oracleの公式ドキュメントでは、コンパイル時には存在したクラス定義が実行時には見つからない場合に投げられると説明されています。

項目内容
エラーメッセージException in thread “main” java.lang.NoClassDefFoundError: パッケージ名/クラス名(多くの場合、直下にCaused by: java.lang.ClassNotFoundExceptionが続く)
発生する言語・環境Java全般。Oracle JDK 8およびOpenJDK 21の公式APIドキュメントで同様の説明を確認済み。Android実行環境(ART)でも発生するが表示形式が異なる
エラーの意味JVMまたはClassLoaderがクラスの定義を読み込もうとしたが、そのクラスの定義がどこにも見つからなかったという状態
主な原因コンパイル時に存在したJARやクラスファイルが、実行時のクラスパスに含まれていないこと
解決の基本方針スタックトレースのCaused by以下からクラス名を特定し、実行時に使われているクラスパスにそのクラスが含まれているかを確認する

なぜNoClassDefFoundErrorが発生するのか?

NoClassDefFoundErrorは名前の響きからClassNotFoundExceptionの言い換えだと誤解されがちですが、実際にはjava.lang.Errorのサブクラス(正確にはLinkageErrorのサブクラス)であり、コード側が明示的にクラスをロードしようとして失敗するClassNotFoundExceptionとは継承関係も性質も異なります。

NoClassDefFoundErrorの原因は、実務で観測される頻度で見るとおおむね3つに分けられます。もっとも多いのは実行時クラスパスにコンパイル時のJARが含まれていないケースで、次いで静的初期化の失敗、そしてビルドツールのスコープ設定ミスが続きます。以下ではこの順番で、実際のコードと合わせて確認していきます。

実行時クラスパスにJARが含まれていないケース

コンパイル時には確かに存在していたクラスファイルが、何らかの理由で実行時のクラスパスから抜け落ちているケースです。もっとも単純な形で再現できます。

// HelperClass.java
public class HelperClass {
    public static void greet() {
        System.out.println("Hello from HelperClass");
    }
}

// MainClass.java
public class MainClass {
    public static void main(String[] args) {
        HelperClass.greet();
    }
}

// 両方コンパイルすると成功する
javac HelperClass.java MainClass.java

// HelperClass.class だけを削除して実行する(本番でJARの同梱漏れが起きた状態を再現)
rm HelperClass.class
java MainClass
// => Exception in thread "main" java.lang.NoClassDefFoundError: HelperClass
//    Caused by: java.lang.ClassNotFoundException: HelperClass

実行時にMainClass.classは見つかりますが、mainメソッド内でHelperClass.greet()を呼び出す段階になって初めてHelperClassのロードが必要になり、そこで見つからずに失敗します。これがNoClassDefFoundErrorの最も基本的な発生メカニズムです。

// HelperClass.class を実行時クラスパスに含めて実行し直す
javac HelperClass.java MainClass.java
java -cp . MainClass
// => Hello from HelperClass

静的初期化ブロックの失敗が原因のケース

1回目のアクセス時に静的初期化(static初期化子や静的変数の代入)が例外で失敗すると、Java言語仕様上そのクラスは初期化失敗状態として記録されます。以降そのクラスへアクセスするたびに、初期化を再試行することなくNoClassDefFoundErrorが投げられます。

// ConfigLoader.java
public class ConfigLoader {
    // 100 / 0 は定数式として畳み込まれず、クラス初期化時に評価される
    static final int MAX = 100 / 0;

    public static void printMax() {
        System.out.println(MAX);
    }
}

// MainApp.java
public class MainApp {
    public static void main(String[] args) {
        try {
            ConfigLoader.printMax(); // 1回目: 静的初期化でArithmeticExceptionが起きExceptionInInitializerErrorになる
        } catch (Throwable t) {
            System.out.println("1回目: " + t);
        }
        try {
            ConfigLoader.printMax(); // 2回目以降: 初期化は再試行されずNoClassDefFoundErrorになる
        } catch (Throwable t) {
            System.out.println("2回目: " + t);
        }
    }
}
// 出力例:
// 1回目: java.lang.ExceptionInInitializerError
// 2回目: java.lang.NoClassDefFoundError: Could not initialize class ConfigLoader

ここで重要なのは、2回目に表示されるCould not initialize classというメッセージです。原因をClassNotFoundExceptionやクラスパスだけに求めてしまうと、この種の失敗にはたどり着けません。静的初期化ブロックが例外を投げないよう作り直すのが根本的な対処になります。

// ConfigLoader.java(修正版)
public class ConfigLoader {
    // 例外を発生させない安全な値にする
    static final int MAX = 100;

    public static void printMax() {
        System.out.println(MAX);
    }
}
// 呼び出し側はそのままでよい。静的初期化が一度成功すれば、
// 以降のアクセスでNoClassDefFoundErrorになることはない

ビルドツールのスコープ設定ミスによるケース

Mavenのprovidedスコープは、コンパイル時とテスト実行時にはクラスパスに含まれるものの、実行可能なアーティファクト(jarやwar)には同梱されないという性質を持ちます。この性質を理解せずに使うと、IDE上のテストは通るのに、実際にビルドした成果物を動かすとNoClassDefFoundErrorになります。

<!-- pom.xml(誤った設定) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
    <scope>provided</scope> <!-- 本来は実行時にも必要なのにprovidedにしてしまっている -->
</dependency>

// mvn package && java -jar app.jar を実行すると
// => Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils
//    Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.StringUtils
<!-- pom.xml(修正版) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
    <!-- scopeを省略するとcompileスコープとなり、実行時にも同梱される -->
</dependency>
ITKAGYO運営者デプロイ太郎のアイコン画像

【デプロイ太郎のコメント】静的初期化の失敗が2回目からNoClassDefFoundErrorに化けるのは、知らないと本当に混乱するパターンです。心当たりがあれば1回目の呼び出しログを探してみてください。

NoClassDefFoundErrorは実行環境によって表示が変わる?

NoClassDefFoundErrorは、JVMのバージョンや実行基盤によってスタックトレースやメッセージの見た目が変わることがあります。実際にWeb上で確認できた範囲の違いを次の表にまとめました。

実行環境表示されるエラーメッセージ・スタックトレースの特徴
JDK 8以前(モジュールシステム導入前)Caused by: java.lang.ClassNotFoundExceptionのスタックトレースがjava.net.URLClassLoader.findClass(…)のような表記から始まる
JDK 9以降(Java Platform Module System導入後)同じCaused by部分がjava.base/jdk.internal.loader.BuiltinClassLoader.loadClass(…)のように、モジュール名(java.base/)付きの表記になる
Android実行環境(ART)メッセージがjava.lang.NoClassDefFoundError: Failed resolution of: Lパッケージ/クラス名;という独自の内部表記形式になる

Spring BootやTomcatでNoClassDefFoundErrorが出るのはなぜ?

Spring Bootでの発生パターン

Spring Bootアプリケーションをjava -jarで実行する運用は非常に一般的ですが、依存ライブラリを実行可能jarに同梱するspring-boot-maven-pluginrepackageゴールが設定されていないと、依存関係が同梱されない薄いjarが作られてしまいます。IDEの実行ボタンでは正常に起動するため、この問題は本番デプロイの段階で初めて表面化しがちです。

<!-- pom.xml(pluginの設定が抜けている例) -->
<build>
    <plugins>
        <!-- spring-boot-maven-pluginのrepackageが設定されていない -->
    </plugins>
</build>
// mvn package
// java -jar target/myapp-0.0.1.jar
// => Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication

<!-- pom.xml(修正版) -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
// mvn package
// java -jar target/myapp-0.0.1.jar
// => 正常起動(依存JARがBOOT-INF/libに同梱される)
Spring Bootでは spring-boot:run では問題なく動いても、java -jar で本番稼働させる場合はrepackageの有無を必ず確認するとよいでしょう。

Tomcat(サーブレットコンテナ)での発生パターン

WARファイルとしてTomcatにデプロイする構成では、IDEの実行機能が裏でクラスパスを自動的に補ってくれるため、ローカルでは正常に動作します。ところがmvn packageで作成した実際のWARファイルには、必要なライブラリがWEB-INF/libに含まれていないことがあり、Tomcat起動時やリクエスト処理の途中でNoClassDefFoundErrorが発生します。

<!-- pom.xml: Tomcatが提供しないのにprovidedにしてしまっている例 -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-utils</artifactId>
    <version>1.2.0</version>
    <scope>provided</scope>
</dependency>

// mvn package で作成したROOT.warをTomcatのwebappsに配置して起動すると
// カタリナのログに次のように出力される
// Exception in thread "..." java.lang.NoClassDefFoundError: com/example/utils/DateHelper
IDEのRun on Serverで動いても、実際にビルドしたWARファイルをTomcatに手動でデプロイして必ず動作確認するとよいでしょう。

表示されるクラス名別に対処法は変わる?

Failed resolution of: L…; と表示される場合(Android)

Android実行環境(ART)では通常のJVMと表示形式が異なり、java.lang.NoClassDefFoundError: Failed resolution of: Lcom/example/Foo;のような内部形式のクラス名で表示されます。原因はJVMと同じくクラスが見つからないことですが、Androidの場合はメソッド数の上限に伴うMultiDex設定の漏れが典型的な原因の一つです。

// build.gradle.kts(MultiDex対応が漏れている例)
android {
    defaultConfig {
        // multiDexEnabled true が抜けていると
        // メソッド数の多いプロジェクトで一部クラスの解決に失敗し
        // NoClassDefFoundErrorになることがある
    }
}
dependencies {
    implementation("androidx.multidex:multidex:2.0.1")
}
// 修正: defaultConfig内にmultiDexEnabled trueを追加し、
// Applicationクラスの起動処理でMultiDex.install(this)を呼び出す

外部ライブラリのクラス名が表示される場合

表示されるクラス名が自作クラスではなく外部ライブラリのパッケージパスであれば、原因はほぼ依存関係の欠落かバージョンの不一致です。mvn dependency:treeなどで、そのライブラリが実際にビルド成果物へ含まれているかを確認します。

mvn dependency:tree -Dincludes=org.apache.commons:commons-lang3
// 出力にcommons-lang3が表示されない場合、依存関係の宣言漏れか
// scope設定ミスが疑われる

NoClassDefFoundErrorはどうデバッグすればいいのか?

NoClassDefFoundErrorの調査は、スタックトレースの一番下にあるCaused by:の行を読むところから始まります。そこに表示されるクラス名が実際に見つからなかったクラスです。さらにJVMには、どのクラスをどのファイルから読み込んだかを全て出力する-verbose:classオプションがあり、目的のクラスが期待通りのjarから読み込まれているかを直接確認できます。

java -verbose:class -jar app.jar > verbose.log 2>&1
grep HelperClass verbose.log
// verbose.logの中にHelperClassの読み込みログがあるか、
// またどのjarファイルから読み込まれたかを確認する

// 実行時に有効なクラスパスをプログラムから直接確認する方法
public class ClasspathChecker {
    public static void main(String[] args) {
        System.out.println(System.getProperty("java.class.path"));
    }
}
ITKAGYO運営者デプロイ太郎のアイコン画像

【デプロイ太郎のコメント】-verbose:classは地味なオプションですが、どのjarから読み込まれたか一発でわかる頼れる存在です。原因不明のときほど活躍してくれます。

NoClassDefFoundErrorを未然に防ぐには?

NoClassDefFoundErrorを未然に防ぐいちばんの近道は、手作業でjavac・javaのクラスパスを組み立てるのをやめて、MavenやGradleなど依存関係を宣言的に管理できるビルドツールに任せることです。加えて、CIパイプラインには単体テストだけでなく、実際にビルドした成果物(jarやwar)を起動して疎通確認するスモークテストを組み込んでおくと、パッケージング漏れをリリース前の段階で検知できます。

# CIでのスモークテスト例(ビルド成果物を実際に起動して確認する)
mvn -B package
java -jar target/app.jar --smoke-test
# アプリ起動直後にNoClassDefFoundErrorが出ればここで検知でき、
# 本番デプロイ前に気づける
依存関係のscopeはcompile・provided・runtime・testそれぞれの意味を理解した上で、最小限の変更にとどめるのが安全です。

NoClassDefFoundErrorとClassNotFoundExceptionの違いを深掘りすると何がわかるのか?

NoClassDefFoundErrorは、原因を正しく切り分けるうえでjava.lang.LinkageErrorのサブクラスであるという継承関係を押さえておくと理解が深まります。チェック例外であるClassNotFoundExceptionとは異なり、アプリケーションコードが意図的に発生させたり、通常のロジックでリカバリしたりする対象ではありません。Java言語仕様上も、クラスの初期化に一度失敗すると、そのクラスは以降ずっと初期化失敗状態のまま扱われ、再試行は行われません。

そのためNoClassDefFoundErrorは、単なる例外処理の対象というより、ビルドやデプロイの構成不備を知らせるシグナルとして扱うのが実務的です。Caused byの内容を丁寧に追い、根本原因がクラスパスの欠落なのか、静的初期化の失敗なのかを見極めることが、再発防止につながります。

ITKAGYO運営者デプロイ太郎のアイコン画像

【デプロイ太郎のコメント】ここまでお疲れさまでした。原因さえ切り分けられれば、NoClassDefFoundErrorは決して怖いエラーではありません。落ち着いて一つずつ確認していきましょう。

よくある質問

Q
本番環境でだけNoClassDefFoundErrorが発生するのはなぜですか?
A

開発環境とはクラスパスの構成が異なることが最大の原因です。IDEはプロジェクト内の依存関係を自動的にクラスパスへ含めてしまうため、Maven・Gradleのscope設定ミスや依存関係の同梱漏れがあってもローカルでは気づけません。実際にビルドした成果物(jarやwar)を使って動作確認することが有効です。

Q
Spring BootアプリでNoClassDefFoundErrorが出た場合、最初に確認すべきことは何ですか?
A

実行しているjarファイルのBOOT-INF/libフォルダに該当ライブラリが含まれているかを確認します。あわせてspring-boot-maven-pluginのrepackageゴールが実行されているかどうかもチェックするとよいでしょう。

Q
mvn dependency:treeなどのツールで事前に防ぐことはできますか?
A

はい、可能です。mvn dependency:treeやgradle dependenciesで依存関係とscopeを可視化し、必要なライブラリがcompileまたはruntimeスコープになっているかを確認できます。CIにビルド成果物のスモークテストを組み込むことも有効な予防策です。

Q
NoClassDefFoundErrorをtry-catchで捕捉してユーザーに通知しても問題ありませんか?
A

技術的にはjava.lang.Errorのサブクラスなのでcatch(Throwable)で捕捉できますが、根本的な解決にはなりません。NoClassDefFoundErrorはアプリケーションの構成不備を示すシグナルであるため、ユーザー向けの通知よりも、デプロイの検証プロセスで事前に検知する仕組みを整える方が重要です。

Q
NoClassDefFoundErrorとClassNotFoundExceptionの違いは何ですか?
A

最大の違いは、NoClassDefFoundErrorがjava.lang.Errorのサブクラスであるのに対し、ClassNotFoundExceptionはチェック例外である点です。ClassNotFoundExceptionはClass.forName()やClassLoader.loadClass()など、コード側が明示的にクラスをロードしようとして失敗したときに投げられます。一方NoClassDefFoundErrorは、コンパイル時に存在したクラスをJVMが暗黙的にロードしようとして失敗したときに発生し、その内部原因としてClassNotFoundExceptionがCaused byに現れることもあります。

この記事と一緒に知っておきたいエラー解決

関連エラー この記事との関連
Java NullPointerException 同じJavaのランタイムエラーで、スタックトレースの読み方や原因の切り分け方が共通する
ClassNotFoundException NoClassDefFoundErrorと混同されやすい例外で、両者の違いを理解するとCaused by以下の読み方が変わる
Python ModuleNotFoundError 言語は異なるが、実行時に定義が見つからずインポート・ロードに失敗するという構造が共通する
PHP Fatal error: Uncaught Error: Class ‘X’ not found クラス定義が見つからないという症状が同種で、オートロードや同梱設定を疑う考え方が参考になる
TypeScript Cannot find module ビルド時と実行時でモジュール解決の結果が食い違うという点で、原因調査のアプローチが似ている

【出典】参考URL

https://docs.oracle.com/javase/8/docs/api/java/lang/NoClassDefFoundError.html:NoClassDefFoundErrorの公式定義(Java SE 8)
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/NoClassDefFoundError.html:NoClassDefFoundErrorの公式定義(Java SE 21)
https://www.digitalocean.com/community/tutorials/java-lang-noclassdeffounderror:クラスパス不一致やMavenのprovidedスコープが原因になる実例
https://coderanch.com/t/655737/certification/ExceptionInInitializerError-NoClassDefFoundError:静的初期化失敗後にNoClassDefFoundErrorとなる挙動の解説(JLSに基づく議論)

コメント

デプロイ太郎のSNSを見てみる!!