【2025年】JavaFX アプリの実行可能ファイル化 (Windows, Eclipse)

# 1: はじめに
本資料では Java がインストールされていない環境で、Eclipse で開発した自作プログラムを .exe ファイルとして実行できるようにする方法を紹介しています。

## 1-1: 参考資料
参考資料として紹介している記事を読んでいただくのが一番早いかと思います。もう少し丁寧に、ということでまとめ直させていただきました。

* [Qiita - JavaFX & 自己完結型パッケージでのツール作成（配布編）](https://qiita.com/ketman55/items/a954dfc2b748559f0920)

## 1-2: 実施環境
* Windows 11 Pro
* Eclipse 2023-12
* e(fx)clipse 3.8.0
* Oracle OpenJDK 21
* JavaFX 21
* Launch4j 3.50

# 2: 作業方法

## 2-1: サンプル Eclipse プロジェクトについて
サンプルの Eclipse プロジェクト名は「HelloFXSample」です。  
これから、「HelloFXSample.exe」として実行できるようにしていきます。

動作は、ボタンがクリックされたら "Hello JavaFX World!" を表示するだけの単純なものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/9cd46699-8961-0434-dac7-0f1152f65096.png)

```java:SampleController.java (コントローラクラス)
package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

public class SampleController {
	@FXML private Button button;
	@FXML private Label label;
	
	@FXML void onButtonClicked() {
		label.setText("Hello JavaFX World!");
	}
}
```

```xml:Sample.fxml (ビュー設計)
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="200.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SampleController">
   <children>
      <Button fx:id="button" layoutX="25.0" layoutY="55.0" mnemonicParsing="false" onAction="#onButtonClicked" text="Button" />
      <Label fx:id="label" layoutX="25.0" layoutY="100.0" text="Label" />
   </children>
</AnchorPane>
```

## 2-2: Eclipse プロジェクトのエクスポート
exe ファイル化したいプロジェクトを右クリックし、エクスポートを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/6888db80-46e4-1105-55cd-5372f7584c69.png)

「実行可能JARファイル」を選択し、次へ進みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/322e6dd4-5081-2467-bdfd-716779f88500.png)

必要な設定を行い、JARファイルを生成します。

| 項目 | 選択 |
| ---- | ---- |
| 起動構成 | Main (ドロップダウンリストにある) |
| エクスポート先 | JAR ファイルの保存先 |
| ライブラリー処理 | 生成されるJARの隣のサブフォルダーに必須ライブラリーをコピー |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/3cf267bf-61cd-d1be-04e0-aec83daf4bc2.png)

エクスポートすると、保存先に JAR ファイルと外部ライブラリ入りのフォルダが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/caa0cf79-1ef3-7103-bcf7-1a0d6dc5a892.png)

## 2-3: アプリ実行用の最小構成 JRE を作成する
Java の実行には JRE (Java Runtime Environment) が必要です。開発環境の JRE をそのままコピーしても動作しますが、配布用なので、なるべく最小限のサイズにしたいということで、最小構成の JRE を作成します。

### 2-3-1: jdeps によるモジュールの依存関係の確認
jdeps コマンドを使い、モジュールの依存関係を確認します。jdeps コマンドは JDK に含まれているため、パスが通っていれば利用できます。

```: コマンド書式 
> jdeps --module-path [JavaFX SDK の lib ディレクトリ] -s [JAR ファイル]
```

`--module-path` オプションの [JavaFX SDK の lib ディレクトリ] には、インストールしているはずの [JavaFX SDK](https://gluonhq.com/products/javafx/) の lib ディレクトリのパスを指定します。複数パスを指定する場合は `;` で区切れば良さそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/63cae138-02c5-d37f-61b1-5ce9cb95d8ea.png)

こちらの環境では、SDK を "C:\pleiades\javafx-sdk-21.0.2" に格納しているので、次のように指定しました。
```: コマンド実行例
> jdeps --module-path "C:\pleiades\javafx-sdk-21.0.2\lib" -s HelloFXSample.jar

HelloFXSample -> java.base
HelloFXSample -> javafx.base
HelloFXSample -> javafx.controls
HelloFXSample -> javafx.fxml
HelloFXSample -> javafx.graphics
```

最小構成 JRE 化するにあたって、jdeps コマンドで列挙された項目を、次で使う jlink コマンドの `--add-modules` に指定します。

* java.base （不要）
* javafx.base
* javafx.controls
* javafx.fxml
* javafx.graphics （参考資料によると、不要とのこと）



:::note info
#### 自作モジュールを含む場合

自作モジュールを含む場合は、次のようなエラーが出るかもしれません。

```: 失敗例
> jdeps --module-path C:\pleiades\javafx-sdk-21.0.2\lib -s HelloFXSample.jar

Exception in thread "main" java.lang.module.FindException: Module ImportedSample2 not found, required by JarImportSample2
        at java.base/java.lang.module.Resolver.findFail(Resolver.java:892)
        at java.base/java.lang.module.Resolver.resolve(Resolver.java:192)
        at java.base/java.lang.module.Resolver.resolve(Resolver.java:141)
        at java.base/java.lang.module.Configuration.resolve(Configuration.java:420)
        at java.base/java.lang.module.Configuration.resolve(Configuration.java:254)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsConfiguration$Builder.build(JdepsConfiguration.java:564)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.buildConfig(JdepsTask.java:607)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:561)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:537)
        at jdk.jdeps/com.sun.tools.jdeps.Main.main(Main.java:50)
```

自作モジュールがあるとき、JAR ファイルのエクスポート時、JAR ファイルの隣に生成されている `～～_lib` フォルダに、依存する JAR ファイルがコピーされています。
そこで、 `～～_lib` ファイルも `--module-path` に `;` で区切って指定します。  
**コマンド実行環境 (PowerShellなど) によっては `;` がコマンドの区切りとして認識されてしまうため、ダブルクォートします。**

```: 成功例
> jdeps --module-path "C:\pleiades\javafx-sdk-21.0.2\lib;HelloFXSample" -s HelloFXSample.jar

HelloFXSample -> ImportedSample2 (自作モジュール)
HelloFXSample -> java.base (無視してOK)
HelloFXSample -> javafx.base
HelloFXSample -> javafx.controls
HelloFXSample -> javafx.fxml
HelloFXSample -> javafx.graphics (無視してOK)
```

ここでは ImportedSample2 モジュールにも依存していることが分かります。

* ImportedSample2 （必要）
* java.base （不要）
* javafx.base
* javafx.controls
* javafx.fxml
* javafx.graphics （参考資料によると、不要とのこと）

:::

### 2-3-2: jlink コマンドによる JRE の作成
jlink コマンドを使い、配布アプリケーション専用の JRE を作成します。

```: コマンド書式
> jlink --module-path [JavaFX の jmods ディレクトリ] --add-modules java.base,javafx.base,javafx.controls,javafx.fxml --output jre-min
```

`--module-path` オプションの [JavaFX の jmods ディレクトリ] には [JavaFX SDK](https://gluonhq.com/products/javafx/) 配布サイトでダウンロードできる jmods ファイルのディレクトリを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/1c5de657-bde5-501b-bcdb-0654e04da700.png)

`--add-modules` オプションには jdeps コマンドで見つかった依存関係のライブラリをカンマ区切りで指定します。

```: コマンド実行例
> jlink --module-path "C:\pleiades\javafx-jmods-21.0.2" --add-modules java.base,javafx.base,javafx.controls,javafx.fxml --output jre-min
```

うまく行けば jre-min ディレクトリが生成されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/f0c9a0a4-4ab5-8d8d-b888-a5ff7fe79f01.png)


:::note info
#### 自作モジュールを含む場合

自作モジュールを含む場合は、次のようなエラーが発生するかもしれません。
```: 失敗例
> jlink --module-path c:\pleiades\javafx-jmod
s-21.0.2\ --add-modules java.base,javafx.base,javafx.controls,javafx.fxml,ImportedSample2 --ou
tput jre-min
エラー: Module ImportedSample2 not found
java.lang.module.FindException: Module ImportedSample2 not found
        at java.base/java.lang.module.Resolver.findFail(Resolver.java:892)
        at java.base/java.lang.module.Resolver.resolve(Resolver.java:129)
        at java.base/java.lang.module.Configuration.resolve(Configuration.java:420)
        at java.base/java.lang.module.Configuration.resolve(Configuration.java:254)
        at jdk.jlink/jdk.tools.jlink.internal.Jlink$JlinkConfiguration.resolve(Jlink.java:217)
        at jdk.jlink/jdk.tools.jlink.internal.JlinkTask.createImageProvider(JlinkTask.java:523)
        at jdk.jlink/jdk.tools.jlink.internal.JlinkTask.createImage(JlinkTask.java:411)
        at jdk.jlink/jdk.tools.jlink.internal.JlinkTask.run(JlinkTask.java:286)
        at jdk.jlink/jdk.tools.jlink.internal.Main.run(Main.java:56)
        at jdk.jlink/jdk.tools.jlink.internal.Main.main(Main.java:34)
```

こちらも jdeps コマンドのときと同様に `--module-path` に `～～_lib` フォルダを指定しましょう。そして、 `--add-modules` オプションに自作モジュールも追記することを忘れないようにしましょう。

```: 成功例
> jlink --module-path "c:\pleiades\javafx-jmods-21.0.2\;HelloFXSample_lib/ImportedSample2.jar" --add-modules java.base,javafx.base,javafx.controls,javafx.fxml,ImportedSample2 --output jre-min
```

続きの手順は、同じ操作で作成できるはずです。

:::

## 2-4: JARファイルを動作確認する
PowerShell または cmd.exe で JAR ファイルを保存したディレクトリで作業します。
次のようなコマンドで、JAR ファイルからプログラムが実行できることを確認します。

```: コマンド書式
> .\jre-min\bin\java.exe -jar [JAR ファイル]
```

```: コマンド実行例
> .\jre-min\bin\java.exe -jar HelloFXSample.jar
```

## 2-5: EXE ファイルに変換する
変換には Launch4j を使用します。次のサイトからダウンロードし、インストールしてください。

[http://launch4j.sourceforge.net/](http://launch4j.sourceforge.net/)

Basicタブでは次のような設定を行います。
| 設定項目 | 設定内容 |
| ------- | ------- |
| Output file | 出力する exe ファイル名を設定します。JRE から相対パスで指定できるよう jre-min フォルダの横に指定します。
| Jar | 変換するJARファイルを指定します
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/173b992a-35e8-8f11-31e2-00eb4724e9d0.png)

JREタブでは次のような設定を行います。

| 設定項目 | 設定内容 |
| ------- | ------- |
| JRE paths | exe ファイルを基準に jre-min ディレクトリを相対パスで指定します
| Min JRE version | 必須項目です。21.0.2 のように Java のバージョンを指定します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/800e8b74-9e8e-ba49-21de-1ee5e67275e4.png)

最後に、画面上部の歯車マーク (Build Wrapper) で設定ファイルを保存し、実行します。実行時のログに "Successfully created" があれば成功しているはずです。

画面上部の再生マーク (Test Wrapper) で実行して動作を確認します。

作業ディレクトリにある、jre-min ディレクトリと exe ファイルを配布することで、どこでも実行できます。
こちらの環境では、lib ディレクトリや jar ファイルは必要ありませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/eab6a145-060d-d62b-e437-a8063c9f1dcc.png)

# 3: おまけ（自作モジュールを含むプロジェクト）

JavaFX プロジェクトを作成するとき、自作プログラムをインポートしようとしてもうまく動かないことがあるかもしれません。  
そして、原因の一つに、Java 9 から導入されたモジュールシステムがあるかもしれません。

Java 9 からはパッケージなし（デフォルトパッケージ）は非推奨となっており、パッケージなしの JAR ファイルをインポートするとうまく動かなくなります。

これから説明する失敗を起こさないためには、何かしらのパッケージに所属させ、module_info.java を適切に指定してください。

## 3-1: 再現手順 (非推奨のデフォルトパッケージで作ってしまう)

まず、EXE ファイル化しようとする Eclipse プロジェクトは、次のような ImportedSample プロジェクトで作成された JAR ファイルをインポートしていたとします。

### 3-1-1: インポートされる ImportedSample プロジェクトの作成

動作としては、main メソッドを実行すると、sayHello メソッドの戻り値 "Hello.sayHello" を表示するだけです。

Eclipse プロジェクトの状態（デフォルトパッケージの状態）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/6969d8b3-bdce-a34f-927c-b1dd0fb34e43.png)

```java:Hello.java
public class Hello {
	public static void main(String[] args) {
		System.out.println(sayHello());
	}
	public static String sayHello() {
		return "Hello.sayHello";
	}
}
```

ImportedSample プロジェクトを、他の Java プロジェクトとして使えるように JAR ファイル化します。  
JAR ファイル化の手順は、JavaFX プロジェクトのときと同様ですが、エクスポート時の処理にて「生成される JAR に必須ライブラリをパッケージ」を選択しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/1c04c932-5b59-bedf-5dc3-a873dec2f6ff.png)

次のようなコマンドで、生成した JAR ファイル単体の動作確認を行います。
```
> java -jar ImportedSample.jar
Hello.sayHello (出力結果)
```

### 3-1-2: プロジェクトの作成と JAR ファイルのインポート

JAR ファイルをインポートする JavaFX プロジェクトを作成します。  
今回は JarImportSample という名前で作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/5b9a568f-17cd-9833-3a83-cf15b2b51f3a.png)

JarImportSample のフォルダに、先ほど作成した JAR ファイルをコピーしておきます。（図を見るとツリーに JAR ファイルが追加されている）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/fe440d70-b258-bd94-67c7-94c127ab4995.png)

プロジェクト名を右クリック > [ビルド・パス] > [ビルド・パスの構成] を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/d26b6fbd-daba-f791-54ac-169e3ed8cb95.png)

次のようにプログラムを書き換え、JAR でインポートした Hello クラスを利用しようとしてみます。

```java:SampleController.java (コントロールクラス)
package application;

import Hello;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

public class SampleController {
	@FXML private Button button;
	@FXML private Label label;
	
	@FXML void onButtonClicked() {
		label.setText(Hello.sayHello());
	}
}
```

残念ながら、Hello クラスが見つからないと言われてしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/c5a6db06-82c5-0cbe-e6c4-488ccfc56288.png)

## 3-2: 原因

こちらで解説されています。（すみません。あまり理解していません）

https://qiita.com/opengl-8080/items/93c8e0cf58654d5f73cb

## 3-3: 解決方法 

### 3-3-1: インポートされる ImportedSample2 プロジェクトの再作成

ImportedSample プロジェクトの作り方が間違っていますので、ImportedSample2 として作り直します。

注意点としては、次のとおりです。
* クラスはなにかしらのパッケージの中につくること
  * 今回は application2 パッケージに所属させています (わざと別名)
  * つまり、Hello.java は application2 フォルダの中に作成されています
* module-info.java を作成する
  * Eclipse ならプロジェクト作成時のオプションで指定できます
  * モジュール名は、パッケージ名と同じにしました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/395143ac-3589-c654-31d5-c00228c52ae9.png)

Hello.java の先頭には `package application2;` の追加が必要です。
```java:Hello.java
package application2;
public class Hello {
	public static void main(String[] args) {
		System.out.println(sayHello());
	}
	public static String sayHello() {
		return "Hello.sayHello2";
	}
}
```

module-info.java には、application パッケージの公開設定を指定します。
```java:module-info.java
module ImportedSample2 {
    exports application2;
}
```

先ほどと同様の手順で JAR ファイルを生成し直し、単体で動作確認します。

```
> java -jar ImportedSample2.jar
Hello.sayHello2 (出力結果)
```

### 3-3-2: プロジェクトの作成と JAR ファイルのインポート

うまく動いたら、JAR ファイルを JavaFX プロジェクトで読み込み直してみましょう。  
ここでは新しく作り直して JarImportSample2 としています。

次のようにプログラムを書き換え、JAR でインポートした Hello クラスを利用します。

```java:SampleController.java (コントロールクラス)
package application;

import application2.Hello;  // application2 パッケージに所属し直したため
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

public class SampleController {
	@FXML private Button button;
	@FXML private Label label;
	
	@FXML void onButtonClicked() {
		label.setText(Hello.sayHello());
	}
}
```

この時点では、`application2/Hello` が存在しないと言われてしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125577/b70bc44a-f96f-3f04-5a83-d1093705c3cb.png)

module-info.java の中身を次のように書き換え、`ImportedSample2` モジュールを読み込むように指定します。

```java:module-info.java
module JarImportSample2 {
	requires javafx.controls;
	requires javafx.fxml;
	requires ImportedSample2;
	
	opens application to javafx.graphics, javafx.fxml;
}
```

すると、動くようになっているはずです。