- JAVA
jlink・jpackageの使い方|最小JREとインストーラ作成
jlink は「アプリに必要なモジュールだけを入れた Java ランタイム(JRE)」を作るツール、jpackage はそのランタイムとアプリを1つにまとめて、配布用のフォルダやインストーラ(deb・rpm・msi・exe・dmg・pkg)を作るツールです。 どちらも JDK に入っています(jlink は JDK 9、jpackage は JDK 16 から正式)。
手順は3つです。
# 1. 必要なモジュールを調べる
jdeps --print-module-deps --class-path 'lib/*' lib/app.jar
# → java.base,java.sql
# 2. 最小ランタイムを作る
jlink --add-modules java.base,java.sql \
--strip-debug --no-header-files --no-man-pages --compress=zip-6 \
--output runtime
# 3. アプリと一緒に配布用のフォルダ(またはインストーラ)にする
jpackage --type app-image --name report --input lib --main-jar app.jar \
--runtime-image runtime --dest dist
手元で作ったランタイムのサイズです(JDK 25.0.4.1、Linux x86_64)。
| 中身 | オプション | サイズ |
|---|---|---|
| JDK 25 一式(比較用) | ― | 330MB |
| java.se(標準 API 全部) | なし | 104MB |
| java.base + java.net.http + アプリ | なし | 61MB |
| 同上 | --strip-debug --compress=zip-6 など4つ |
42MB |
| jpackage の deb(上の 46MB のランタイム入り) | ― | 23.8MB |
確認環境:Ubuntu 24.04(x86_64)、OpenJDK 21.0.10 と 25.0.4.1(Ubuntu パッケージ)。jdeps・jlink・jpackage を実際に動かし、サイズは du -sk で測りました。オプションの説明は jlink --help・jpackage --help の出力と Oracle の JDK 25 ドキュメント、JEP 392・JEP 493 で確かめました(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「jlink・jpackageの使い方|最小JREとインストーラ作成」
jlink・jpackage・jdeps の役割
| ツール | すること | できるもの |
|---|---|---|
| jdeps | クラスファイルを読み、使っている JDK のモジュールを調べる | モジュール名の一覧(java.base,java.sql など) |
| jlink | 指定したモジュールだけで Java ランタイムを組み立てる | bin/java を含むフォルダ(独自の JRE) |
| jpackage | ランタイムとアプリの jar を、起動用の実行ファイルつきでまとめる | app-image(フォルダ)、deb・rpm・msi・exe・dmg・pkg |
モジュールとは、Java 9 で入った「パッケージをまとめた単位」です。JDK 自体も java.base(String や List などの基本)、java.sql(JDBC)、java.net.http(HttpClient)のように約70のモジュールに分かれています。jlink は、この中から必要なものだけを選んでランタイムを作ります。 自分のアプリをモジュールにする書き方は、関連記事「module-info.javaの書き方|requires・exports一覧」で扱っています。
手順① jdeps で必要なモジュールを洗い出す
jdeps --print-module-deps は、jlink の --add-modules にそのまま渡せる形で、必要なモジュールを出力します。
例として、java.sql.Timestamp と java.util.logging.Logger を使う、モジュール化していない(module-info.java の無い)普通の jar を調べました。
package com.example.report;
import java.sql.Timestamp;
import java.util.logging.Logger;
public class App {
public static void main(String[] args) {
Logger log = Logger.getLogger("report");
log.info("now=" + new Timestamp(System.currentTimeMillis()));
System.out.println("java.home=" + System.getProperty("java.home"));
}
}
$ jdeps --print-module-deps lib/report.jar
java.base,java.sql
$ jdeps -s lib/report.jar
report.jar -> java.base
report.jar -> java.logging
report.jar -> java.sql
-s(概要)では java.logging も出ますが、--print-module-deps では出ませんでした。 java.sql が java.logging を requires transitive で読み込むため、java.sql を入れれば java.logging も自動で入るからです。jlink に渡すのは --print-module-deps の結果で足ります。
jdeps のオプション早見表
| オプション | 意味 |
|---|---|
--print-module-deps |
jlink 用に、必要なモジュールをカンマ区切りで出す |
--list-deps |
必要なモジュールを1行ずつ出す |
-s(-summary) |
jar ごとに、どのモジュールに依存しているかの概要 |
--class-path 'lib/*' |
依存ライブラリの jar の場所 |
--recursive |
依存ライブラリの中まで、たどって調べる |
--multi-release 21 |
マルチリリース jar のどの版を調べるか |
--ignore-missing-deps |
見つからないクラスがあっても続ける(後述の注意あり) |
落とし穴:依存ライブラリを渡さないと、必要なモジュールが漏れる
アプリ本体(main.jar)が、別の jar(util.jar)のクラスを呼び、その util.jar が XML の API(java.xml)を使う構成で試しました。
$ jdeps --print-module-deps lib/main.jar
Error: Missing dependencies: classes not found from the module path and classpath.
To suppress this error, use --ignore-missing-deps to continue.
main.jar
com.example.main.Main -> com.example.util.Xml not found
エラーの案内どおり --ignore-missing-deps を付けると、結果は java.base だけになり、java.xml が抜けました。 このまま作ったランタイムで動かすと、実行時に止まります。
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/parsers/DocumentBuilderFactory
at com.example.util.Xml.parse(Xml.java:4)
at com.example.main.Main.main(Main.java:3)
依存の jar を --class-path で渡すと、正しく java.base,java.xml になりました。
| 書き方 | 結果 |
|---|---|
jdeps --print-module-deps lib/main.jar |
Error: Missing dependencies |
... --ignore-missing-deps lib/main.jar |
java.base(java.xml が漏れる) |
... --class-path 'lib/*' lib/main.jar |
java.base,java.xml |
... lib/main.jar lib/util.jar(全部並べる) |
java.base,java.xml |
--ignore-missing-deps は「調べられなかったクラスを無視する」オプションなので、そのクラスが使うモジュールは数に入りません。 ライブラリを含めて調べるには、jar を全部渡します。リフレクションや ServiceLoader で読み込むクラスは jdeps では見えないため、作ったランタイムで一度アプリを通して動かして確かめます。
手順② jlink で最小ランタイムを作る
jlink --add-modules java.base,java.sql \
--strip-debug \
--no-header-files \
--no-man-pages \
--compress=zip-6 \
--output runtime
できたフォルダの runtime/bin/java が、そのまま使える java コマンドです。
$ runtime/bin/java --list-modules
java.base@25.0.4.1
java.logging@25.0.4.1
java.sql@25.0.4.1
java.transaction.xa@25.0.4.1
java.xml@25.0.4.1
--add-modules で指定したモジュールが読み込む(requires する)モジュールは、自動で入ります。--module-path を書かないと、JDK の jmods フォルダが使われます。
オプションごとのサイズ(実測)
java.base・java.net.http・自作モジュールの3つで、オプションを1つずつ変えて JDK 25 で作りました。
| オプション | 全体 | lib/modules |
|---|---|---|
| なし | 62,188KB | 31,024KB |
--strip-debug |
57,620KB | 26,456KB |
--compress=zip-6 |
45,632KB | 14,468KB |
--no-header-files --no-man-pages |
61,668KB | 31,024KB |
| 上の4つ全部 | 42,816KB | 12,168KB |
4つ全部(zip-9) |
42,788KB | 12,144KB |
一番効いたのは --compress=zip-6(クラスなどの圧縮)で、約16MB 減りました。 zip-9 にしても zip-6 との差は 28KB でした。残りの大部分は JVM 本体の lib/server/libjvm.so(28,584KB)で、これはモジュールを減らしても変わりません。
JDK 21 で同じものを作ると、何も付けないと 59,424KB、4つ全部で 39,924KB でした。
jlink の主なオプション一覧
| オプション | 意味 |
|---|---|
--add-modules <mod,...> |
入れるモジュール。ALL-MODULE-PATH も書ける |
--module-path(-p) |
自作モジュールの jar の場所。JDK の jmods は自動で足される |
--output <dir> |
出力先。既にあるとエラー |
--strip-debug(-G) |
デバッグ情報(行番号など)を消す |
--compress=zip-0〜9 |
圧縮。JDK 21 以降の書き方。付けないと圧縮されない |
--no-header-files / --no-man-pages |
C のヘッダーと man ページを入れない |
--launcher 名前=モジュール[/クラス] |
bin/名前 の起動スクリプトを作る |
--include-locales=ja,en |
入れるロケールを絞る(jdk.localedata と一緒に) |
--bind-services |
ServiceLoader の実装モジュールも入れる |
--generate-cds-archive |
起動を速くする CDS アーカイブを作る |
--strip-native-commands |
bin/java などのコマンドを入れない(jpackage 向け) |
--compress の 0・1・2 という以前の書き方は、JDK 25 で使うと次の警告が出ました。
Warning: The 2 argument for --compress is deprecated and may be removed in a future release
jlink --help では 2 は zip-6 と同じ、と説明されています。
自作モジュールと起動スクリプト(--launcher)
アプリ自体がモジュール(module-info.java あり)なら、ランタイムの中にアプリも入れて、起動用のスクリプトを作れます。
javac -d out --module-source-path src -m com.example.hello
jar --create --file hello.jar --main-class com.example.hello.Main -C out/com.example.hello .
jlink --module-path hello.jar --add-modules com.example.hello \
--launcher hello=com.example.hello \
--strip-debug --no-header-files --no-man-pages --compress=zip-6 \
--output runtime
$ runtime/bin/hello
Hello from 25.0.4.1+1-1-24.04.4-Ubuntu / modules=3 / http=HTTP_2
作られた bin/hello は、java -m com.example.hello/com.example.hello.Main を呼ぶだけのシェルスクリプトでした。jar --main-class で指定したメインクラスが jar に記録されているため、--launcher ではモジュール名だけで足りています。
手順③ jpackage で配布用にまとめる
app-image(フォルダ)を作る
--type app-image は、インストーラを作らず、そのまま動くフォルダを作ります。 中身を確かめるのに向いています。
jpackage --type app-image \
--name report \
--input lib \
--main-jar report.jar \
--runtime-image runtime \
--dest dist
dist/report/
├── bin/report ← 起動用の実行ファイル(ネイティブ)
└── lib/
├── app/report.cfg ← 起動設定
├── app/report.jar ← --input の中身
├── libapplauncher.so
├── report.png
└── runtime/ ← --runtime-image のコピー
$ dist/report/bin/report
INFO: now=2026-10-13 15:49:05.228
java.home=/…/dist/report/lib/runtime
report.cfg の中身は次のとおりでした。--java-options を付けると、ここに足されます。
[Application]
app.mainjar=$APPDIR/report.jar
[JavaOptions]
java-options=-Djpackage.app-version=1.0
--runtime-image を付けないと、ランタイムは大きくなる
--runtime-image を付けずに、モジュール化していない jar を渡すと、jpackage が自分で jlink を呼び、標準のモジュールを広く入れます。
| 作り方 | 入ったモジュール数 | app-image のサイズ |
|---|---|---|
jdeps → jlink → --runtime-image |
5 | 46,928KB |
--runtime-image なし(非モジュールの jar) |
52 | 134,588KB |
--runtime-image なし(--module でモジュール指定) |
3 | 57,376KB |
jpackage --help によると、--runtime-image が無いときの jlink のオプションは --strip-native-commands --strip-debug --no-man-pages --no-header-files です。--compress が入っていないので、自分で jlink した方が小さくなります。 jlink のオプションを変えたいときは、--jlink-options で渡せます。
deb を作る(Linux)
jpackage --type deb --name report --app-version 1.0.0 \
--input lib --main-jar report.jar \
--runtime-image runtime --linux-shortcut --dest dist
| 項目 | 結果 |
|---|---|
| できたファイル | report_1.0.0_amd64.deb(23,751,570 バイト) |
| インストール先 | /opt/report/(bin/report で起動) |
Depends |
libc6, libgcc-s1, libstdc++6, xdg-utils, zlib1g |
| 作成にかかった時間 | 約15秒 |
fakeroot が入っていないと、deb は作られず次のメッセージで止まりました。
Bundler DEB Bundle skipped because of a configuration problem: Can not find fakeroot.
Reason: Cannot run program "fakeroot": Exec failed, error: 2 (No such file or directory)
Advice to fix: Please install required packages
Ubuntu では apt-get install fakeroot で入れたあと、作れました。インストールして /opt/report/bin/report を動かすと、java.home=/opt/report/lib/runtime と表示され、同梱のランタイムで動いていました。
OS ごとの --type と前提
| OS | --type |
前提(ドキュメントより) |
|---|---|---|
| Linux | deb・rpm・app-image |
Ubuntu は fakeroot、Red Hat は rpm-build |
| Windows | exe・msi・app-image |
WiX 3.0 以降 |
| macOS | dmg・pkg・app-image |
署名(--mac-sign)や DMG のアイコン変更には Xcode コマンドラインツール |
Oracle のドキュメントには「各形式は、それが動く OS の上で作る必要があり、別の OS 向けには作れない」とあります。Windows 用の exe は Windows で、macOS 用の dmg は macOS で作ります。CI では OS ごとにジョブを分けます。
Windows でよく使うオプションは --win-menu(スタートメニュー)、--win-shortcut(デスクトップ)、--win-dir-chooser(インストール先の選択)、--win-console(コンソール画面を出す)、--win-upgrade-uuid(上書き更新の識別子)です。
最小ランタイムで起きやすい問題
日本語の曜日・通貨が英語になる
java.base だけのランタイムでは、日本語のロケールデータが入らず、曜日が英語になりました。
DateTimeFormatter.ofPattern("yyyy年M月d日(E)", Locale.JAPAN).format(LocalDate.of(2026, 10, 13));
NumberFormat.getCurrencyInstance(Locale.JAPAN).format(1234);
| ランタイム | 曜日 | 通貨 |
|---|---|---|
| JDK 25 一式 | 2026年10月13日(火) |
¥1,234 |
java.base のみ |
2026年10月13日(Tue) |
JP¥ 1,234 |
java.base,jdk.localedata + --include-locales=ja |
2026年10月13日(火) |
¥1,234 |
jdeps は jdk.localedata を出さないので、自分で足します。 --include-locales=ja,en で絞ると、全ロケール(52,540KB)より小さい 42,456KB でした。日付の書式は、関連記事「Java DateTimeFormatterパターン一覧|日付と文字列の変換」で扱っています。
よく出るエラー
| メッセージ | 原因 | 直し方 |
|---|---|---|
Error: directory already exists: runtime |
--output の先が既にある |
消してから作り直す |
Error: automatic module cannot be used with jlink: report from file:///… |
module-info の無い jar を --module-path に渡した |
jar は --add-modules に入れず、jpackage の --input で渡す |
Error: Module java.sqll not found |
モジュール名の打ち間違い、または --module-path に無い |
java --list-modules で名前を確かめる |
Error: --output must be specified |
--output が無い |
--output を付ける |
NoClassDefFoundError(実行時) |
必要なモジュールが入っていない | jdeps に依存の jar も渡して調べ直す |
JDK 24 以降の「jmods なしの jlink」(JEP 493)
JDK 24 の JEP 493 で、jmods フォルダが無い JDK からでも jlink できる仕組みが入りました。JDK の大きさが約25%小さくなる、と書かれています。ただし既定では無効で、JDK をビルドするときの設定(--enable-linkable-runtime)で有効になります。 使えるかどうかは jlink --help の最後に出る行で分かります。
Capabilities:
Linking from run-time image disabled
今回の JDK 25(Ubuntu パッケージ)は disabled で、jmods(86MB)が入っていました。有効な JDK でも、jdk.jlink を含むランタイムは作れない、conf の設定ファイルを書き換えていると失敗する、別 OS 向けには作れない、といった制限が JEP に書かれています。
よくある質問
Q. Windows 用のランタイムを Linux の jlink で作れますか。
A. JEP 493 には「jmods なしの方式では、別 OS 向けのランタイムは作れない」とあります。jmods を使う従来の方式では、対象 OS の JDK の jmods を --module-path に渡して作る方法が使われます。jpackage は前述のとおり、その OS の上で作ります。
Q. Spring Boot の fat jar(実行可能 jar)でも使えますか。
A. fat jar は jar の中に依存の jar(BOOT-INF/lib)が入った形です。展開して、中の jar を --class-path で jdeps に渡して調べる方法がよく使われます。必要なモジュールが分かれば、jlink の手順は同じです。
Q. jlink で作ったランタイムに JVM オプションを付けられますか。
A. --launcher の起動スクリプトには JLINK_VM_OPTIONS= の行があり、--add-options プラグインでも足せます。jpackage では --java-options が .cfg に書かれます。JVM オプションの一覧は、関連記事「JVMオプション一覧|Xmx・Xms・GCログ設定をJava 21対応で」で扱っています。
Q. jar の作り方は?
A. jar --create --file app.jar --main-class パッケージ.クラス -C classes . です。詳しくは関連記事「Java jarファイルの作成と実行方法|クラスパス指定とMain-Class」で扱っています。
まとめ
- jdeps → jlink → jpackage の順。
jdeps --print-module-depsの結果を--add-modulesに渡す - jdeps には 依存の jar も渡す。
--ignore-missing-depsだけだとモジュールが漏れた - サイズは
--compress=zip-6が一番効いた。4つのオプションで 62MB → 43MB - jpackage に
--runtime-imageを渡さないと 52 モジュール・135MB になった - 日本語の曜日・通貨には
jdk.localedataを足す - インストーラは その OS の上で作る。Linux の deb には fakeroot が要る
参考・出典
確認日はいずれも 2026年10月13日です。
- Oracle「The jlink Command」(JDK 25):https://docs.oracle.com/en/java/javase/25/docs/specs/man/jlink.html
- Oracle「The jpackage Command」(JDK 25):https://docs.oracle.com/en/java/javase/25/docs/specs/man/jpackage.html
- Oracle「The jdeps Command」(JDK 25):https://docs.oracle.com/en/java/javase/25/docs/specs/man/jdeps.html
- OpenJDK「JEP 392: Packaging Tool」:https://openjdk.org/jeps/392
- OpenJDK「JEP 493: Linking Run-Time Images without JMODs」:https://openjdk.org/jeps/493
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「jlink・jpackageの使い方|最小JREとインストーラ作成」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。