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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?