- JAVA
Java jarファイルの作成と実行方法|クラスパス指定とMain-Class
jarファイルとは、コンパイル済みのclassファイルをまとめたZIP形式のファイルです。作成は jar cfe アプリ.jar メインクラス名 -C 出力フォルダ .、実行は java -jar アプリ.jar の2つのコマンドで完結します。
つまずきやすいのは、実行したときに出るエラーです。この記事では Java 21(OpenJDK 21.0.10、Linux)で「メイン・マニフェスト属性がありません」「メイン・クラスを検出およびロードできませんでした」「NoClassDefFoundError」を実際に出し、原因と直し方を対応表にしました。あわせて、-jar を付けると -cp が無視されること、OSごとの区切り文字、マニフェスト末尾の改行、Class-Path の書き方を、実行結果で1つずつ確かめます。掲載した出力は、断りのない限り手元で実行したものです。
jarコマンドの早見表(作成・実行・確認)
| やりたいこと | コマンド | 補足 |
|---|---|---|
| コンパイルする | javac -d out src/com/example/hello/Main.java |
classファイルを out フォルダに出力 |
| 実行できる jar を作る | jar cfe app.jar com.example.hello.Main -C out . |
e の後ろにメインクラスの完全な名前。マニフェストが自動で作られる |
| 同じことを長いオプションで | jar --create --file app.jar --main-class com.example.hello.Main -C out . |
意味は cfe と同じ |
| jar を実行する | java -jar app.jar |
マニフェストの Main-Class が起動される |
| 引数を渡して実行する | java -jar app.jar Taro |
jar 名の後ろがプログラムへの引数 |
| クラスパスを指定して実行する | java -cp app.jar:lib/greeter.jar com.example.hello.Main |
Mac/Linux は : 区切り。メインクラス名を自分で書く |
| 同上(Windows) | java -cp app.jar;lib\greeter.jar com.example.hello.Main |
Windows は ; 区切り(今回は未実行) |
| フォルダ内の jar をまとめて指定 | java -cp "app.jar:lib/*" com.example.hello.Main |
lib/* は引用符で囲む |
| ライブラリ用の jar を作る(メインなし) | jar cf lib/greeter.jar -C lib-out . |
Main-Class は入らない |
| 自分で書いたマニフェストを使う | jar cfm app.jar manifest.txt -C out . |
m の後ろにマニフェストのファイル名 |
| 既存の jar にマニフェストの属性を足す | jar ufm app.jar add.txt |
Class-Path をあとから足すときに便利 |
| 中身の一覧を見る | jar tf app.jar |
詳細つきは jar tvf |
| マニフェストを取り出して確認する | jar xf app.jar META-INF/MANIFEST.MF |
カレントディレクトリに展開される |
| エラー文(Java 21 の日本語表示) | 主な原因 | 直し方 |
|---|---|---|
app.jarにメイン・マニフェスト属性がありません |
マニフェストに Main-Class がない | jar cfe で作り直す |
エラー: メイン・クラス○○を検出およびロードできませんでした |
クラス名のつづり・パッケージ名・クラスパスのどれかが合っていない | 完全な名前(パッケージ名.クラス名)と区切り文字を確認 |
java.lang.NoClassDefFoundError: com/example/util/Greeter |
依存ライブラリがクラスパスにない | -cp に足す、またはマニフェストに Class-Path を書く |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java jarファイルの作成と実行方法|クラスパス指定とMain-Class」
最短の手順でjarを作って実行する
まずはファイル1つで、作成から実行までを通します。
// Solo.java
public class Solo {
public static void main(String[] args) {
System.out.println("Solo OK " + String.join(",", args));
}
}
javac -d solo-out Solo.java
jar cfe solo.jar Solo -C solo-out .
java -jar solo.jar a b
実行結果:
Solo OK a,b
jar cfe の文字は、それぞれ次の意味です。オプションの文字の順番と、後ろに並べる値の順番をそろえるのがポイントです。
| 文字 | 意味 | 対応する値 |
|---|---|---|
| c | 新しく作る(create) | なし |
| f | ファイル名を指定する(file) | solo.jar |
| e | 起動するクラスを指定する(entry point) | Solo |
-C solo-out . |
solo-out フォルダに移動して、その中身すべて(.)を入れる |
なし |
中身を確認します。
jar tf solo.jar
unzip -p solo.jar META-INF/MANIFEST.MF
META-INF/
META-INF/MANIFEST.MF
Solo.class
Manifest-Version: 1.0
Created-By: 21.0.10 (Ubuntu)
Main-Class: Solo
META-INF/MANIFEST.MF がマニフェスト(jar の説明書き)で、その中の Main-Class: Solo が「java -jar で起動するクラス」 です。
パッケージと外部ライブラリがある場合の作り方
次に、より本番に近い形として、パッケージつきのメインクラスが別の jar(ライブラリ)を使う構成を作ります。以降のエラー再現もこの構成を使います。
a2/
├── src/com/example/hello/Main.java … アプリ本体
├── libsrc/com/example/util/Greeter.java … ライブラリ側
└── lib/greeter.jar … ライブラリの jar(これから作る)
// libsrc/com/example/util/Greeter.java
package com.example.util;
public class Greeter {
public static String greet(String name) {
return "Hello, " + name + "!";
}
}
// src/com/example/hello/Main.java
package com.example.hello;
import com.example.util.Greeter;
public class Main {
public static void main(String[] args) {
String name = args.length > 0 ? args[0] : "RJC";
System.out.println(Greeter.greet(name));
}
}
# 1. ライブラリをコンパイルして jar にする(メインクラスなし)
javac -d lib-out libsrc/com/example/util/Greeter.java
mkdir -p lib
jar cf lib/greeter.jar -C lib-out .
# 2. アプリ本体を、ライブラリをクラスパスに入れてコンパイルする
javac -cp lib/greeter.jar -d out src/com/example/hello/Main.java
# 3. 実行できる jar を作る
jar cfe app.jar com.example.hello.Main -C out .
# 4. クラスパスに両方を並べて実行する
java -cp app.jar:lib/greeter.jar com.example.hello.Main Taro
実行結果:
Hello, Taro!
メインクラスは「パッケージ名.クラス名」の完全な名前(完全修飾名)で指定します。Main だけでは見つかりません(後半のエラー再現で確かめます)。
-jarと-cpの違いを理解する
jar の実行方法は2通りあります。
| 項目 | java -jar app.jar |
java -cp ... メインクラス名 |
|---|---|---|
| メインクラスの指定 | マニフェストの Main-Class | コマンドに自分で書く |
| クラスパス | その jar(とマニフェストの Class-Path)だけ | -cp に書いたものすべて |
-cp や環境変数 CLASSPATH |
無視される | 使われる |
| 向いている場面 | 配布して1コマンドで起動してもらう | 開発中の確認、ライブラリを差し替えて試す |
-jarを付けると-cpは無視される(実行結果)
「-cp でライブラリを足したのに NoClassDefFoundError になる」という場面は、ほとんどがこの組み合わせです。実際に試します。
java -cp lib/greeter.jar -jar app.jar
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/util/Greeter
at com.example.hello.Main.main(Main.java:8)
Caused by: java.lang.ClassNotFoundException: com.example.util.Greeter
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
... 1 more
-cp lib/greeter.jar を書いているのに、Greeter が見つかっていません。環境変数で CLASSPATH=lib/greeter.jar java -jar app.jar としても同じ結果でした。
実際にクラスパスがどうなっているかを、システムプロパティで表示して確かめます。
// Cp.java
public class Cp {
public static void main(String[] a) {
System.out.println("java.class.path=" + System.getProperty("java.class.path"));
System.out.println("path.separator=" + System.getProperty("path.separator"));
}
}
javac -d cp-out Cp.java
jar cfe cp.jar Cp -C cp-out .
java -cp "lib/greeter.jar:out" -jar cp.jar
java -cp "cp.jar:lib/greeter.jar" Cp
java.class.path=cp.jar
path.separator=:
java.class.path=cp.jar:lib/greeter.jar
path.separator=:
-jar を付けた1つ目は、-cp に書いた内容が消えて cp.jar だけになっています。今回 Oracle の java コマンドのドキュメント(Java 21)を開いて確認したところ、-jar について「指定した JAR ファイルがすべてのユーザークラスのソースとなり、他のクラスパス設定は無視される」と説明されています。
また、オプションの位置にも注意します。java -jar app.jar -cp lib/greeter.jar と jar 名の後ろに書いた場合は、-cp と lib/greeter.jar が「プログラムへの引数」として渡されるだけで、同じ NoClassDefFoundError になりました。
直し方は2つ
| 直し方 | コマンド・設定 | 特徴 |
|---|---|---|
| A. -jar をやめて -cp で実行する | java -cp app.jar:lib/greeter.jar com.example.hello.Main |
すぐ試せる。メインクラス名を書く必要がある |
| B. マニフェストに Class-Path を書く | Class-Path: lib/greeter.jar(後述) |
java -jar app.jar のまま動く |
クラスパスの区切り文字(Windowsは; Mac/Linuxは:)
複数の jar やフォルダを並べるときの区切り文字は、OSによって違います。
| OS | 区切り文字 | 例 | 今回の検証 |
|---|---|---|---|
| Linux / macOS | :(コロン) |
java -cp app.jar:lib/greeter.jar com.example.hello.Main |
Linux で実行して確認 |
| Windows | ;(セミコロン) |
java -cp app.jar;lib\greeter.jar com.example.hello.Main |
未実行(Windows 環境がないため) |
Oracle の java コマンドのドキュメントにも、--class-path の説明として「Windows ではセミコロン、他のプラットフォームではコロンで区切る」と書かれています。先ほどの Cp.java で表示した path.separator=: が、その環境の区切り文字です。
区切り文字を取り違えるとどうなるか(Linuxで実行)
Windows 向けの手順をそのまま Linux で使った場合を再現します。
java -cp "app.jar;lib/greeter.jar" com.example.hello.Main
Error: Could not find or load main class com.example.hello.Main
Caused by: java.lang.ClassNotFoundException: com.example.hello.Main
Linux では ; が区切りとして扱われないため、app.jar;lib/greeter.jar という名前の1つのファイルを探しに行き、メインクラスが見つからなくなります。なお、引用符で囲まずに書くと、シェルが ; を「コマンドの区切り」と解釈するため、意図と違う動きになります。
「クラス名は合っているのにメイン・クラスが見つからない」ときは、区切り文字を確認すると解決することがあります。
フォルダ内のjarをまとめて指定する
jar が多いときは、フォルダ名/* でそのフォルダ内の jar をまとめて指定できます。
java -cp "app.jar:lib/*" com.example.hello.Main
Hello, RJC!
| ルール | 内容 |
|---|---|
| 引用符で囲む | 囲まないと、シェルが lib/* をファイル名の並びに展開してしまう |
| 対象 | そのフォルダ直下の .jar/.JAR ファイル(ドキュメントで確認)。サブフォルダは対象外 |
| 読み込む順番 | 決まっていない(ドキュメントで確認)。同じクラスが複数の jar にある構成は避ける |
| マニフェストの Class-Path では使えない | 後述の実行結果のとおり |
マニフェストの書き方(Main-ClassとClass-Path)
マニフェストを自分で書くと、Main-Class に加えて Class-Path(一緒に読み込む jar)を指定できます。
Main-Class: com.example.hello.Main
Class-Path: lib/greeter.jar
これを manifest.txt という名前で保存し(最後の行の後ろで必ず改行する)、jar cfm で取り込みます。
jar cfm app-cp.jar manifest.txt -C out .
unzip -p app-cp.jar META-INF/MANIFEST.MF
java -jar app-cp.jar
Manifest-Version: 1.0
Main-Class: com.example.hello.Main
Class-Path: lib/greeter.jar
Created-By: 21.0.10 (Ubuntu)
Hello, RJC!
-cp なしの java -jar だけで、ライブラリも読み込まれました。
マニフェストの書式ルール
| ルール | 守らないとどうなるか(Java 21.0.10 で確認) |
|---|---|
名前: 値 の形で、コロンの後ろに半角スペースを1つ |
Main-Class:com.example.hello.Main と書くと、jar 作成時に java.io.IOException: invalid header field (line 1) |
| 最後の行の後ろで改行する | 最後の行が取り込まれない(次の項で再現) |
Main-Class は完全な名前で、.class は付けない |
実行時に「メイン・クラスを検出およびロードできませんでした」 |
| Class-Path は半角スペース区切りで複数書ける | : や ; で区切ると1つの名前として扱われる |
| Class-Path の相対パスは jar ファイルの場所が起点 | jar だけを別フォルダに移すとライブラリが見つからない(後述) |
末尾の改行がないと最後の行が消える(実行結果)
改行のないマニフェストを2種類作って確かめました。printf は末尾に改行を付けないので、再現に使えます。
# (1) 1行だけで、末尾に改行なし
printf 'Main-Class: com.example.hello.Main' > m1.txt
jar cfm app-m1.jar m1.txt -C out .
java -jar app-m1.jar
app-m1.jarにメイン・マニフェスト属性がありません
# (2) 2行で、2行目の末尾に改行なし
printf 'Main-Class: com.example.hello.Main\nClass-Path: lib/greeter.jar' > manifest-nonl.txt
jar cfm app-nonl.jar manifest-nonl.txt -C out .
unzip -p app-nonl.jar META-INF/MANIFEST.MF
java -jar app-nonl.jar
Manifest-Version: 1.0
Main-Class: com.example.hello.Main
Created-By: 21.0.10 (Ubuntu)
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/util/Greeter
(以下略)
| ケース | 消えた行 | 出たエラー |
|---|---|---|
| (1) Main-Class の行に改行なし | Main-Class | メイン・マニフェスト属性がありません |
| (2) Class-Path の行に改行なし | Class-Path | NoClassDefFoundError |
jar の作成自体はエラーなく終わり、実行して初めて気づくのがこの問題の特徴です。作成後に unzip -p app.jar META-INF/MANIFEST.MF(または jar xf で取り出す)で、書いた行が入っているかを確認するのが確実です。
Class-Pathの相対パスはjarの場所が起点
Class-Path に書いた lib/greeter.jar は、「コマンドを実行したフォルダ」ではなく「jar ファイルがあるフォルダ」から見たパスです。
# 別のフォルダから実行する(jar と lib の位置関係はそのまま)
mkdir -p elsewhere && cd elsewhere
java -jar ../app-cp.jar Hanako
Hello, Hanako!
# jar だけを別フォルダにコピーして実行する(lib は元の場所のまま)
mkdir -p moved && cp app-cp.jar moved/
java -jar moved/app-cp.jar
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/util/Greeter
(以下略)
配布するときは、jar と lib フォルダの位置関係を保ったまま渡します。
長いClass-Pathは自動で折り返される
ライブラリを3つ並べたマニフェストを取り込んで、中身を確認しました(2つ目と3つ目は実在しない名前です)。
Manifest-Version: 1.0
Main-Class: com.example.hello.Main
Class-Path: lib/greeter.jar lib/commons-text-1.99.0.jar lib/another-long
-name-library-2.0.0.jar
Created-By: 21.0.10 (Ubuntu)
1行が長くなると、jar コマンドが途中で折り返し、次の行の先頭に半角スペースを1つ入れます。これはマニフェストの「継続行」の書式で、読み込むときは1行につなげて扱われます。この jar も Hello, RJC! と表示して動きました(存在しない jar は読み飛ばされます)。自分で折り返しを書く必要はなく、1行で書いて jar コマンドに任せれば十分です。
Class-Pathでワイルドカードは使えない
Main-Class: com.example.hello.Main
Class-Path: lib/*
このマニフェストで作った jar を実行すると、NoClassDefFoundError になりました。lib/* が使えるのは -cp のほうだけで、マニフェストの Class-Path には jar の名前を1つずつ書きます。
既存のjarにClass-Pathを足す
作り直さずに、あとから属性を足すこともできます。
printf 'Class-Path: lib/greeter.jar\n' > add.txt
jar ufm app.jar add.txt
unzip -p app.jar META-INF/MANIFEST.MF
java -jar app.jar
Manifest-Version: 1.0
Created-By: 21.0.10 (Ubuntu)
Main-Class: com.example.hello.Main
Class-Path: lib/greeter.jar
Hello, RJC!
実行時エラーを再現して直す
ここからは、エラーを1つずつ実際に出して、原因と直し方を確認します。メッセージは英語表示と日本語表示の両方を載せます(日本語は -Duser.language=ja -Duser.country=JP を付けて表示)。
「メイン・マニフェスト属性がありません」
jar cf solo2.jar -C solo-out . # e を付けずに作成
java -jar solo2.jar
no main manifest attribute, in solo2.jar
solo2.jarにメイン・マニフェスト属性がありません
| 原因 | 直し方 |
|---|---|
jar cf で作った(Main-Class が入らない) |
jar cfe solo2.jar Solo -C solo-out . で作り直す |
| マニフェストの Main-Class の行に改行がない | 最後の行の後ろで改行して作り直す |
| ライブラリ用の jar を -jar で実行しようとした | java -cp その.jar メインクラス名 で実行する |
| ビルドツールで Main-Class の設定をしていない | 後述の Gradle/Maven の設定を足す |
Main-Class がない jar でも、-cp で実行すれば動きます。
java -cp solo2.jar Solo a b
Solo OK a,b
「メイン・クラスを検出およびロードできませんでした」
Java 21 の日本語表示は「エラー: メイン・クラス○○を検出およびロードできませんでした」です。英語では Could not find or load main class で、バージョンによっては日本語の言い回しが少し異なります。原因ごとに実行結果を並べます。
(1) マニフェストの Main-Class のつづりが違う(Main を Mian と書いた)
jar cfe bad.jar com.example.hello.Mian -C out .
java -jar bad.jar
Error: Could not find or load main class com.example.hello.Mian
Caused by: java.lang.ClassNotFoundException: com.example.hello.Mian
エラー: メイン・クラスcom.example.hello.Mianを検出およびロードできませんでした
原因: java.lang.ClassNotFoundException: com.example.hello.Mian
jar の作成時には、クラスが実在するかはチェックされません。エラー文に出ているクラス名を、ソースの package 文・クラス名と1文字ずつ見比べます。
(2) パッケージ名を省いた
java -cp app.jar:lib/greeter.jar Main
Error: Could not find or load main class Main
Caused by: java.lang.ClassNotFoundException: Main
(3) .class を付けた
java -cp app.jar:lib/greeter.jar com.example.hello.Main.class
Error: Could not find or load main class com.example.hello.Main.class
Caused by: java.lang.ClassNotFoundException: com.example.hello.Main.class
(4) パッケージの途中のフォルダから実行した(out/com/example に移動して実行)
cd out/com/example
java hello.Main
Error: Could not find or load main class hello.Main
Caused by: java.lang.NoClassDefFoundError: hello/Main (wrong name: com/example/hello/Main)
wrong name は、「ファイルは見つかったが、中に書かれているクラス名(com.example.hello.Main)と指定した名前(hello.Main)が違う」という意味です。クラスパスの起点は、パッケージの一番上のフォルダ(この例では out) にします。
(5) 区切り文字が OS と合っていない … 前述の ; と : の取り違えです。
| 「原因:」の行 | 考えられる原因 | 直し方 |
|---|---|---|
ClassNotFoundException: com.example.hello.Mian |
つづりの間違い | 正しい名前で jar を作り直す |
ClassNotFoundException: Main |
パッケージ名を省いた | com.example.hello.Main と完全な名前で書く |
ClassNotFoundException: ....Main.class |
.class を付けた |
拡張子を外す |
NoClassDefFoundError: ... (wrong name: ...) |
クラスパスの起点がずれている | パッケージの一番上のフォルダを -cp に指定する |
ClassNotFoundException(名前は正しい) |
クラスパスに jar が入っていない、区切り文字が違う | -cp の内容と区切り文字を確認する |
「NoClassDefFoundError」
メインクラスは起動できたものの、途中で使うクラスが見つからないときに出ます。
java -jar app.jar # Class-Path を足す前の app.jar
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/util/Greeter
at com.example.hello.Main.main(Main.java:8)
Caused by: java.lang.ClassNotFoundException: com.example.util.Greeter
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
... 1 more
読み方は、1行目の com/example/util/Greeter が「見つからなかったクラス」、2行目の Main.java:8 が「それを使おうとした場所」です。コンパイルのときには -cp lib/greeter.jar で見えていたクラスが、実行のときには見えていない、という状態です。
今回、同じエラーになった操作をまとめます。
| 実行した操作 | 原因 | 直し方 |
|---|---|---|
java -jar app.jar |
依存 jar がどこにも指定されていない | Class-Path を書く、または -cp で実行 |
java -cp lib/greeter.jar -jar app.jar |
-jar があるので -cp が無視された | 同上 |
java -cp app.jar com.example.hello.Main |
-cp に依存 jar を並べていない | app.jar:lib/greeter.jar と並べる |
| Class-Path の行に改行がないマニフェスト | Class-Path が取り込まれていない | 末尾で改行して作り直す |
| jar だけ別フォルダへ移動 | Class-Path の相対パスの先に jar がない | lib フォルダも一緒に移す |
Class-Path: lib/* |
マニフェストではワイルドカードが使えない | jar 名を1つずつ書く |
そのほかのメッセージ
| メッセージ | 状況 | 直し方 |
|---|---|---|
Error: Unable to access jarfile nothing.jar |
指定した jar ファイルがない(今回実行して確認) | ファイル名とカレントディレクトリを確認する |
java.io.IOException: invalid header field (line 1) |
マニフェストのコロンの後ろにスペースがない(jar 作成時。今回実行して確認) | Main-Class: 名前 とスペースを入れる |
UnsupportedClassVersionError |
jar を作った Java より低いバージョンの Java で実行した(今回は未実行。一般に知られた挙動) | 実行側の Java を上げるか、javac --release 版数 で作り直す |
依存ライブラリごと1つのjarにまとめる
配布を1ファイルで済ませたいときは、ライブラリの class も同じ jar に入れてしまう方法があります(fat jar、uber jar と呼ばれます)。仕組みを理解するために、手作業でやってみます。
mkdir -p fat && cd fat
jar xf ../lib/greeter.jar # ライブラリを展開
rm -rf META-INF # ライブラリ側のマニフェストは使わない
cp -r ../out/* . # アプリ本体の class を重ねる
cd ..
jar cfe app-fat.jar com.example.hello.Main -C fat .
java -jar app-fat.jar
jar tf app-fat.jar
Hello, RJC!
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/hello/
com/example/hello/Main.class
com/example/util/
com/example/util/Greeter.class
-cp も Class-Path もなしで動きました。ライブラリが多い場合は、次のビルドツールのプラグイン(Gradle の Shadow、Maven の Shade や Assembly など)に任せるのが一般的です。ライブラリを同梱して配布するときは、それぞれのライセンスの条件も確認しておきましょう。
| 配布の形 | 起動コマンド | 向いている場面 |
|---|---|---|
| app.jar + lib フォルダ(Class-Path) | java -jar app.jar |
ライブラリを個別に差し替えたい |
| 1つにまとめた jar | java -jar app-fat.jar |
1ファイルで渡したい |
| -cp で都度指定 | java -cp "app.jar:lib/*" メインクラス名 |
開発中の確認 |
GradleとMavenでMain-Classを設定する
日常の開発では、jar コマンドを直接使う代わりにビルドツールに任せることが多くなります。どちらも「マニフェストに Main-Class を書く設定」を足す点は同じです。
Gradle(8.14.3 で実行して確認)
// build.gradle
plugins {
id 'java'
}
version = '1.0.0'
jar {
manifest {
attributes 'Main-Class': 'com.example.hello.Main'
}
}
// settings.gradle
rootProject.name = 'hello'
ソースは src/main/java/com/example/hello/Main.java に置きます。
gradle jar
java -jar build/libs/hello-1.0.0.jar
unzip -p build/libs/hello-1.0.0.jar META-INF/MANIFEST.MF
Hello from build tool!
Manifest-Version: 1.0
Main-Class: com.example.hello.Main
attributes の設定を書かない場合は Main-Class が入らないため、java -jar で「メイン・マニフェスト属性がありません」になります。
Maven(未実行)
Maven では、maven-jar-plugin に mainClass を設定するのが一般的な書き方です。今回の検証環境では Maven のプラグインを取得できなかったため、下の設定は未実行です。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.example.hello.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
mvn package を実行すると target/ フォルダに jar ができる、というのが標準の流れです。できあがった jar は、この記事と同じ方法(unzip -p でマニフェストを見る、java -jar で起動する)で確認できます。
作成後の確認チェックリスト
| 確認すること | コマンド | 見るポイント |
|---|---|---|
| class が正しいフォルダ構成で入っているか | jar tf app.jar |
com/example/hello/Main.class のようにパッケージどおりか |
| Main-Class が入っているか | unzip -p app.jar META-INF/MANIFEST.MF |
完全な名前か、つづりは正しいか |
| Class-Path が入っているか | 同上 | 最後の行が消えていないか |
| 依存 jar が相対パスの先にあるか | ls lib/ |
jar からの相対位置に置かれているか |
| 別のフォルダから起動できるか | cd /tmp && java -jar /パス/app.jar |
カレントディレクトリに頼っていないか |
| 実行する Java のバージョン | java -version |
作成時と同じか、それ以上か |
よくある質問
Q. jar cfe と jar cfm はどちらを使えばよいですか?
A. メインクラスを指定するだけなら jar cfe が手軽です。マニフェストのファイルを用意しなくてよいので、末尾の改行を気にする必要もありません。Class-Path など他の属性も書きたいときは jar cfm を使うか、jar cfe で作ったあとに jar ufm で足します。
Q. -jar と -cp を同時に使いたいときはどうすればよいですか?
A. 同時には効きません。-jar を付けると -cp と環境変数 CLASSPATH は無視されます。ライブラリを足したいときは、-jar をやめて java -cp "app.jar:lib/*" メインクラス名 とするか、マニフェストの Class-Path に書きます。
Q. jarファイルをダブルクリックしても起動しません。
A. まずターミナル(コマンドプロンプト)で java -jar app.jar を実行し、表示されるメッセージを確認するのが近道です。この記事の対応表のどれに当たるかがわかります。ダブルクリックで起動するには、OS 側で .jar と Java の関連付けが必要で、画面を持たないプログラムでは起動しても何も表示されません(この点は今回未検証です)。
Q. コンパイルは通るのに、実行すると NoClassDefFoundError になるのはなぜですか?
A. コンパイル時(javac の -cp)と実行時(java の -cp や Class-Path)で、クラスパスが別々に指定されるためです。コンパイルのときに見えていた jar を、実行のときにも指定します。
まとめ
- 作成は
jar cfe app.jar メインクラスの完全な名前 -C out .、実行はjava -jar app.jar - -jar を付けると -cp と CLASSPATH は無視される。ライブラリは Class-Path に書くか、-cp で実行する
- 区切り文字は Windows が
;、Mac/Linux が:。取り違えると「メイン・クラスを検出およびロードできませんでした」になる - マニフェストは「コロン+半角スペース」と「末尾の改行」が必須。改行がないと最後の行が取り込まれない
- Class-Path は半角スペース区切り、jar の場所が起点、ワイルドカードは使えない
- 「メイン・マニフェスト属性がありません」は Main-Class がない、「NoClassDefFoundError」は依存 jar が見えていない、というサイン
- 作ったら
jar tfとunzip -p app.jar META-INF/MANIFEST.MFで中身を確認する
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java jarファイルの作成と実行方法|クラスパス指定とMain-Class」
株式会社RJCでは、jarファイルの作り方やクラスパスの仕組みを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。