Javaアプリケーションの実行時にクラスが見つからない場合、NoClassDefFoundError と ClassNotFoundException という2つの異なる問題に遭遇することがある。いずれもクラスローディングに関するものだが、その性質や発生タイミングには明確な違いが存在する。
NoClassDefFoundError は java.lang.Error のサブクラスであり、JVMが実行時に必要なクラス定義を解決できなかったことを示す。コンパイル時には存在していたクラスが、実行時のクラスパスに含まれていない場合に典型的に発生する。内部的には、クラスローダーが ClassNotFoundException をキャッチした後、それを NoClassDefFoundError にラップしてスローしている。
一方、ClassNotFoundException はチェック例外で、Class.forName() や ClassLoader.loadClass()、findClass() などを用いた明示的なクラスローディング操作でスローされる。アプリケーションコード内でキャッチして処理することが可能だ。
NoClassDefFoundError の発生例
下記のコードはコンパイル時に Google Guava の Maps クラスを参照している。しかし実行時に Guava のJARをクラスパスに含めない場合、JVMは対象クラスをロードできず NoClassDefFoundError をスローする。
import com.google.common.collect.Maps;
import java.util.Map;
public class MapExample {
public static void main(String[] args) {
Map<String, Integer> sample = Maps.newHashMap();
System.out.println(sample.size());
}
}
コンパイル: javac -cp guava-30.1-jre.jar MapExample.java
誤った実行: java MapExample
この結果、スタックトレースには NoClassDefFoundError: com/google/common/collect/Maps が表示され、その原因として ClassNotFoundException が記録される。これは、JVMがクラス定義を自動的に解決しようとして失敗したものであり、アプリケーション側では直接制御できない。
ClassNotFoundException の発生例
次に、Class.forName() を用いて動的にクラスをロードするケースを示す。ここでは Guava の Lists クラスを指定しているが、クラスパスに当該ライブラリが存在しなければ ClassNotFoundException がスローされる。
public class DynamicLoadSample {
public static void main(String[] args) {
try {
Class<?> target = Class.forName("com.google.common.collect.Lists");
System.out.println("ロード成功: " + target.getName());
} catch (ClassNotFoundException ex) {
System.err.println("クラスが見つかりません: " + ex.getMessage());
}
}
}
このコードをライブラリなしでコンパイル・実行すると、ClassNotFoundException が forName() メソッド内から直接スローされる。呼び出し元で捕捉し、適切な代替処理に繋げることも容易である。
NoClassDefFoundError はJVMが静的に参照解決を行う過程で生じる致命的エラーであり、ClassNotFoundException はプログラマが明示的に実行する動的ロード操作に伴う例外である。これらの違いを理解しておくことで、クラスパス設定の不備や依存関係の欠落に起因するトラブルシューティングの初動が格段に早くなる。