はじめに
「ClassNotFoundException というエラーが出た」「コマンドラインでJavaを実行したら動かなかった」— Javaを始めたばかりだとクラスパスでつまずくことがよくあります。この記事では図解を交えながら、クラスパスの仕組みをゼロからやさしく説明します。
(※一部の文章と図は生成AIを使って作成しています)
目次
- クラスパスとは「地図」のようなもの
- Javaプログラムが動くまでの流れ
- パッケージとフォルダ構造の関係
- コマンドラインでの指定方法
- 環境変数 CLASSPATH での設定(知識として知っておこう)
- JARファイルとクラスパス
- VS Code・Maven・Gradleではどうなる?
- エラーが出たときの対処法
1. クラスパスとは「地図」のようなもの
Javaでプログラムを実行するとき、コンピュータの中で動いている JVM(Java仮想マシン) というソフトがあなたのプログラムを読み込んで実行します。
このJVMは、プログラムに必要なクラスファイル(.class という拡張子のファイル)を探し回るのですが、そのためには 「どのフォルダを探せばいいか」 を知っている必要があります。
そこで登場するのが クラスパス(classpath) です。クラスパスとは「JVMにクラスファイルの場所を教える設定」のことです。
あなたのプログラム
→ MyClass を使いたい!
↓ クラスを探して
JVM(Java仮想マシン)
「クラスパスに書いてあるフォルダを探すぞ」
↓ クラスパスに従って探索
├── /app/classes/
│ MyClass.class ← 見つかった!
└── /app/lib/foo.jar
クラスパスを正しく設定しておかないと、JVMはクラスを見つけられずエラーになります。逆に言えば、クラスパスさえ正しければほとんどのトラブルは発生しないのです。
2. Javaプログラムが動くまでの流れ
まず、Javaのプログラムが実行されるまでの大きな流れを確認しておきましょう。
① ソースコード作成
Main.java(人間が書くテキスト)
│
│ javac コマンド(コンパイル)
▼
② クラスファイル生成
Main.class(JVMが読めるバイナリ)
│
│ java コマンド(実行)
▼
③ JVMが実行
プログラムが動く!
クラスパスは ①コンパイル時 と ③実行時 の両方で必要になります。
| フェーズ | コマンド | クラスパスが必要な理由 |
|---|---|---|
| コンパイル時 | javac |
「このクラスはこんな使い方をするんだな」と型チェックするため、参照するクラスの場所を知る必要がある |
| 実行時 | java |
実際にクラスファイルをメモリに読み込んで実行するため |
どちらか片方だけ設定するのは不完全です。両方でクラスパスが通っている必要があります。
3. パッケージとフォルダ構造の関係
クラスパスを理解する時に押さえておきたいのが「パッケージ」です。Javaにはクラスをグループ分けする「パッケージ」という仕組みがあります。例えば com.example.Main というのは「com.example というパッケージに属する Main というクラス」を意味します。
重要なのは、パッケージ名がそのままフォルダ構造に対応する という点です。
パッケージ名: com.example.Main
│ │ │
▼ ▼ ▼
フォルダ構造: com/example/Main.class
─────────────────────────────────────────
実際のファイルの場所(クラスパスが /app/out の場合)
/app/out/ ← ここがクラスパス
└── com/
└── example/
└── Main.class ← JVMはここを探しに行く
─────────────────────────────────────────
JVMはクラスパスを出発点としてサブフォルダを再帰的に探していきます。つまり、クラスパスに指定するのは「パッケージ構造が始まる一番上のフォルダ」です。com フォルダの親にあたる /app/out/ をクラスパスに指定するのが正しいやり方です。
4. コマンドラインでの指定方法
実際にコマンドラインでクラスパスを指定してみましょう。以下のようなフォルダ構造を例にします。
myapp/
├── src/
│ └── com/
│ └── example/
│ └── Hello.java ← ソースコード
└── out/ ← コンパイル後の出力先
ステップ1:コンパイルする
# -d out/ → コンパイル結果を out/ フォルダに出力する
javac -d out/ src/com/example/Hello.java
ステップ2:実行する
# -cp out/ → out/ フォルダをクラスパスに設定する
# com.example.Hello → 実行するクラスをFQCN(完全な名前)で指定
java -cp out/ com.example.Hello
-cp は -classpath の短縮形です。どちらを使っても同じ意味です。
複数のフォルダを指定したいときは?
クラスパスには複数の場所を同時に指定できます。区切り文字はOSによって異なります。
Mac / Linux: コロン(:)で区切る
java -cp "out/:lib/foo.jar" com.example.Hello
Windows: セミコロン(;)で区切る
java -cp "out/;lib\foo.jar" com.example.Hello
デフォルトのクラスパスは「カレントディレクトリ」
-cp を省略した場合、Javaはコマンドを実行したフォルダ(カレントディレクトリ、.)だけをクラスパスとして扱います。パッケージを使っていない単純なプログラムはこれで動きますが、少し複雑になると指定が必要になります。
5. 環境変数 CLASSPATH での設定(知識として知っておこう)
入門書やネット上のチュートリアルを見ると、「CLASSPATH という環境変数を設定しましょう」という手順が書かれていることがあります。確かにこれも動きますが、実際の開発では使わないことを強くおすすめします。 理由を理解した上で「知識として知っている」状態にしておきましょう。
環境変数 CLASSPATH とは?
OSの「環境変数」という仕組みを使って、クラスパスをあらかじめ登録しておく方法です。設定しておくと、-cp オプションを毎回書かなくても java コマンドが自動でその場所を見てくれます。
# Mac / Linux の場合:~/.bash_profile または ~/.zshrc に追記
export CLASSPATH=/app/out:/app/lib/foo.jar
# Windows の場合:
# 「システムの詳細設定」→「環境変数」→ CLASSPATH を新規作成
# 値: C:\app\out;C:\app\lib\foo.jar
なぜおすすめしないのか?
| 問題 | どういうことか |
|---|---|
| 全プロジェクトに影響する | 環境変数はそのマシン全体に効く。プロジェクトAのために設定した内容がプロジェクトBに干渉し、「さっきまで動いていたのになぜか動かなくなった」が起きやすい |
-cp で完全に無視される |
-cp オプションを付けると環境変数 CLASSPATH は完全に上書きされて無視される。「環境変数を設定したのに効かない」という混乱の原因になる |
| チーム開発では使えない | 環境変数はそのPCだけの設定。他のメンバーのPCには存在しないので、チームで共有できない |
| 現代の開発現場では使わない | Maven や Gradle が当たり前になった現在、環境変数でクラスパスを管理する必要はほぼない。入門書に書かれているのは古い慣習の名残 |
< -cp オプションと環境変数の優先順位 >
java -cp out/ com.example.Main
↑
-cp を指定した場合、環境変数 CLASSPATH は完全に無視される!
java com.example.Main
↑
-cp を省略した場合のみ、環境変数 CLASSPATH が参照される
環境変数 CLASSPATH が設定されているマシンで開発すると、予期しない動作の原因になることがあります。もし過去に設定した覚えがある場合は、削除しておくことをおすすめします。
入門書の環境構築手順で CLASSPATH の設定が出てきたら、「昔の書き方なんだな」と理解した上でスキップしても問題ありません。-cp オプションで代替できます。
6. JARファイルとクラスパス
JARファイル(.jar) とは、複数の .class ファイルをひとまとめにしたZIP形式のアーカイブです。データベース接続などの外部ライブラリ(他の人が作った便利なコード群)は多くの場合JAR形式で配布されています。
gson-2.10.1.jar(中身のイメージ)
└── com/
└── google/
└── gson/
├── Gson.class
├── JsonParser.class
└── ... (たくさんのクラスファイル)
JARファイルをクラスパスに追加するには、フォルダではなく JARファイルそのもの をパスに含めます。
# lib/ フォルダに gson-2.10.1.jar がある場合
javac -cp lib/gson-2.10.1.jar -d out/ src/com/example/Main.java
java -cp "out/:lib/gson-2.10.1.jar" com.example.Main
ワイルドカードで一括指定
JARファイルが増えてきたら、*(アスタリスク)を使ってフォルダ内のJARをまとめて指定できます。
# lib/ フォルダ内のすべての .jar ファイルを含める
java -cp "out/:lib/*" com.example.Main
lib/* は lib/ 直下のJARのみ対象です。サブフォルダの中まで再帰的には検索されません。
7. VS Code・Maven・Gradleではどうなる?
コマンドラインでクラスパスを毎回手で書くのは大変です。実際の開発現場では、ツールがクラスパスを自動的に管理してくれます。
VS Code の場合
VS CodeでJavaを書くには「Extension Pack for Java」という拡張機能をインストールします。これをインストールすると、裏側でLanguage Serverというソフトが動き出し、.vscode/settings.json などを読んでクラスパスを自動で設定してくれます。
外部JARを追加したいときは .vscode/settings.json にこう書きます。
{
"java.project.referencedLibraries": [
"lib/**/*.jar"
]
}
MavenやGradleのプロジェクトならこの設定すら不要で、pom.xml や build.gradle を読んで全自動で設定してくれます。
Mavenの場合
Mavenは pom.xml というファイルで「どのライブラリを使うか」を宣言します。すると必要なJARをインターネットから自動ダウンロードしてクラスパスに追加してくれます。
<!-- pom.xml に追加するだけ。JARのダウンロードも自動! -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
pom.xml に書く
│
▼
Maven が自動で...
① インターネット(Maven Central)からJARをダウンロード
② ~/.m2/repository/ に保存(ローカルキャッシュ)
③ クラスパスに自動追加
Gradleの場合
Gradleも考え方はMavenと同じです。build.gradle に書くだけで同様に自動管理されます。
// build.gradle
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
}
MavenやGradleを使えば、クラスパスを手動で設定する必要はほとんどなくなります。現代のJava開発ではどちらかを使うのが標準的です。
8. エラーが出たときの対処法
クラスパスが原因のエラーは大きく2種類あります。どちらも焦らず確認すれば解決できます。
ClassNotFoundException
Exception in thread "main" java.lang.ClassNotFoundException: com.example.Main
「指定したクラスが見つからない」というエラーです。実行時のクラスパスが間違っている ことがほとんどです。
チェックリスト:
[ ] -cp に指定したフォルダは正しいか?(タイポしていないか)
[ ] コンパイルは成功しているか?(.class ファイルが存在するか)
[ ] クラス名のスペルは正しいか?(大文字小文字も含めて)
[ ] フォルダの区切り文字は合っているか?(: か ; か)
NoClassDefFoundError
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/gson/Gson
「コンパイルは通ったけど実行時にクラスが見つからない」エラーです。コンパイル時と実行時でクラスパスが食い違っている ときに起きます。外部ライブラリのJARをコンパイル時には指定したのに実行時に忘れた、というケースが典型的です。
よくある原因:
・javac -cp に lib/foo.jar を入れたが
java -cp には入れ忘れた ← これ!
解決: コンパイルと実行の -cp を同じ内容にする
「IDEでは動くのにコマンドラインで動かない」
IDEでは問題なく実行できるのに、ターミナルで java コマンドを打つと ClassNotFoundException が出る— これは初心者がよく遭遇する場面です。原因は大きく2つあります。
原因① コマンドを実行しているディレクトリが違う
-cp を省略した場合、クラスパスは コマンドを実行したその場所(カレントディレクトリ) になります。つまり、ターミナルで今いるフォルダが違うだけでクラスが見つからなくなります。
例えば次のようなフォルダ構造を考えてみましょう。
myapp/
├── src/
│ └── com/
│ └── example/
│ └── Hello.java
└── out/ ← Hello.class はここに入っている
└── com/
└── example/
└── Hello.class
この状態で、ターミナルの現在地が myapp/src/ の中などにいると何が起きるでしょうか。
NG — src/ の中にいる場合
$ pwd
/Users/taro/myapp/src ← 今ここにいる
$ java com.example.Hello
Exception in thread "main" java.lang.ClassNotFoundException: com.example.Hello
# src/ の中には .class ファイルがないので見つからない!
OK — out/ の親(myapp/)から -cp を指定する
$ cd /Users/taro/myapp ← myapp/ に移動する
$ java -cp out/ com.example.Hello
Hello, World! ← 動いた!
-cp を省略すると「今いるフォルダ」がクラスパスになります。エラーが出たらまず pwd(Windowsは cd)で今どこにいるかを確認しましょう。
原因② IDEが自動でクラスパスを補完している
VS CodeやIntelliJは、プロジェクトの設定ファイルを読んでクラスパスを自動で組み立ててくれます。そのためIDEの中ではどのフォルダから実行ボタンを押しても動きますが、同じことをコマンドラインで再現しようとすると自分で -cp を指定しなければなりません。
【IDE(VS Code など)の場合】
実行ボタンを押す
↓
IDEがクラスパスを自動組み立て
java -cp "out/:lib/*:..." com.example.Main
^^^^^^^^^^^ ここを自動でやってくれる
【コマンドラインの場合】
$ java com.example.Main
↑ -cp 省略 = カレントディレクトリのみ対象
→ クラスが見つからずエラーになる
Mavenプロジェクトであれば、以下のコマンドで実際に使われているクラスパスを確認できます。これをそのまま -cp に渡すと同じ環境を再現できます。
# 実際のクラスパスを表示する
mvn dependency:build-classpath
# Maven経由でアプリを実行する(クラスパスは自動解決)
mvn exec:java -Dexec.mainClass="com.example.Main"
「コマンドラインで動かしたい」場面が増えてきたら、最初からMavenやGradleを使う習慣をつけると、クラスパスの手動管理から解放されます。
まとめ
「プログラムが実行できないんですけど...」は初心者からよくされる相談の一つです。クラスパスはわかってしまえば難しくはありません。文法も大切ですが、こういった仕組み・理屈を押さえていくことが理解を深めるポイントです。新人の皆さん、頑張ってください!