JavaFX
Java用のモダンなデスクトップアプリケーションフレームワーク。リッチなUI、2D/3Dグラフィックス、メディア再生機能を提供。FXMLによるMVCアーキテクチャとCSS スタイリングをサポート。Swingの後継として位置付けられる。
GitHub概要
openjdk/jfx
JavaFX mainline development
スター3,018
ウォッチ88
フォーク525
作成日:2019年5月6日
言語:C++
ライセンス:GNU General Public License v2.0
トピックス
javajavafxopenjdkopenjfx
スター履歴
データ取得日時: 2025/8/13 01:43
フレームワーク
JavaFX
概要
JavaFXは、Javaプラットフォーム上でリッチなデスクトップアプリケーションを開発するための現代的なGUIツールキットです。OpenJDKプロジェクトの一部として開発されており、高性能なグラフィックス、メディア処理、Webコンテンツの埋め込み、豊富なUIコントロールを提供します。
詳細
JavaFXは2025年現在、OpenJDK 21をベースとした安定したGUIフレームワークとして提供されています。SwingやAWTの後継として位置づけられ、現代的なデスクトップアプリケーション開発に必要な機能を包括的に提供しています。
JavaFXの主要な特徴:
- Scene Graph Architecture: 階層的なシーングラフによる効率的なUI管理
- CSS Styling: Web標準のCSSによるスタイリング機能
- FXML: XMLベースの宣言的UI定義
- Modern Controls: リッチなUIコントロールとチャート
- Hardware Acceleration: GPUアクセラレーションによる高速描画
- Media Support: 音声・動画の再生とメディア処理
2025年現在、多くの企業レベルアプリケーションやツールがJavaFXで開発されており、Javaエコシステムにおける標準的なデスクトップUIフレームワークとしての地位を確立しています。
メリット・デメリット
メリット
- 強力なJavaエコシステム: Javaの豊富なライブラリとツールを活用
- クロスプラットフォーム: Windows、macOS、Linuxで動作
- 宣言的UI: FXMLによる保守性の高いUI設計
- CSSスタイリング: Web開発者に馴染みのあるスタイリング手法
- 高性能グラフィックス: GPU加速による滑らかなアニメーション
- 豊富なコントロール: チャート、表、メディアプレーヤーなど多彩なUI部品
- Scene Builder: ビジュアルなUIデザインツール
- 企業サポート: OpenJDKプロジェクトとして長期サポート
デメリット
- 起動時間: JVMの起動によるアプリケーション開始の遅延
- メモリ使用量: ネイティブアプリと比較して大きなメモリフットプリント
- 配布サイズ: JREを含んだ配布パッケージの大きさ
- モダンUI: 最新のUIデザイントレンドへの対応の遅れ
- 学習コスト: Scene Graph、FXML、CSSの理解が必要
- プラットフォーム固有機能: OS固有の機能へのアクセスが制限される
主要リンク
書き方の例
基本的なJavaFXアプリケーション
// HelloWorldApp.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorldApp extends Application {
@Override
public void start(Stage primaryStage) {
// UIコンポーネントの作成
Label label = new Label("Hello, JavaFX World!");
// レイアウトコンテナの作成
StackPane root = new StackPane();
root.getChildren().add(label);
// シーンの作成
Scene scene = new Scene(root, 400, 300);
// ステージの設定
primaryStage.setTitle("JavaFX Hello World");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
FXML を使用したMVCアーキテクチャ
<!-- MainView.fxml -->
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.example.MainController">
<children>
<HBox spacing="10" alignment="CENTER">
<children>
<Label text="名前:" />
<TextField fx:id="nameField" promptText="名前を入力してください" />
<Button fx:id="greetButton" text="挨拶" onAction="#handleGreetAction" />
</children>
</HBox>
<TextArea fx:id="outputArea" prefRowCount="10" editable="false" />
<HBox spacing="10" alignment="CENTER">
<children>
<Button text="クリア" onAction="#handleClearAction" />
<Button text="終了" onAction="#handleExitAction" />
</children>
</HBox>
</children>
</VBox>
// MainController.java
package com.example;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.application.Platform;
import java.net.URL;
import java.util.ResourceBundle;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class MainController implements Initializable {
@FXML private TextField nameField;
@FXML private Button greetButton;
@FXML private TextArea outputArea;
@Override
public void initialize(URL location, ResourceBundle resources) {
// 初期化処理
outputArea.appendText("アプリケーションが開始されました。\n");
// テキストフィールドが空の時はボタンを無効化
greetButton.disableProperty().bind(
nameField.textProperty().isEmpty()
);
}
@FXML
private void handleGreetAction() {
String name = nameField.getText().trim();
if (!name.isEmpty()) {
String timestamp = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
outputArea.appendText(String.format("[%s] こんにちは、%sさん!\n",
timestamp, name));
nameField.clear();
}
}
@FXML
private void handleClearAction() {
outputArea.clear();
outputArea.appendText("ログがクリアされました。\n");
}
@FXML
private void handleExitAction() {
Platform.exit();
}
}
// MainApp.java
package com.example;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/fxml/MainView.fxml")
);
Scene scene = new Scene(loader.load(), 600, 400);
// CSSスタイルシートの適用
scene.getStylesheets().add(
getClass().getResource("/css/application.css").toExternalForm()
);
primaryStage.setTitle("JavaFX FXML アプリケーション");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
高度なUIコンポーネントの実装
// DataManagementApp.java
package com.example.advanced;
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class DataManagementApp extends Application {
private TableView<Person> tableView;
private ObservableList<Person> data;
private PieChart pieChart;
@Override
public void start(Stage primaryStage) {
// メインレイアウト
BorderPane root = new BorderPane();
root.setPadding(new Insets(10));
// 上部:フォーム
VBox topSection = createFormSection();
// 中央:テーブル
VBox centerSection = createTableSection();
// 右側:チャート
VBox rightSection = createChartSection();
root.setTop(topSection);
root.setCenter(centerSection);
root.setRight(rightSection);
Scene scene = new Scene(root, 1000, 600);
scene.getStylesheets().add(getClass().getResource("/css/advanced.css").toExternalForm());
primaryStage.setTitle("高度なJavaFXアプリケーション");
primaryStage.setScene(scene);
primaryStage.show();
}
private VBox createFormSection() {
VBox formBox = new VBox(10);
formBox.setPadding(new Insets(10));
HBox inputBox = new HBox(10);
TextField nameField = new TextField();
nameField.setPromptText("名前");
Spinner<Integer> ageSpinner = new Spinner<>(1, 100, 25);
ageSpinner.setEditable(true);
ComboBox<String> cityCombo = new ComboBox<>();
cityCombo.setItems(FXCollections.observableArrayList(
"東京", "大阪", "名古屋", "福岡", "札幌", "仙台"
));
cityCombo.setPromptText("都市を選択");
Button addButton = new Button("追加");
addButton.setOnAction(e -> {
if (!nameField.getText().trim().isEmpty() &&
cityCombo.getValue() != null) {
Person person = new Person(
nameField.getText().trim(),
ageSpinner.getValue(),
cityCombo.getValue()
);
data.add(person);
updateChart();
// フォームをクリア
nameField.clear();
ageSpinner.getValueFactory().setValue(25);
cityCombo.setValue(null);
} else {
showAlert("エラー", "すべてのフィールドを入力してください。");
}
});
inputBox.getChildren().addAll(
new Label("名前:"), nameField,
new Label("年齢:"), ageSpinner,
new Label("都市:"), cityCombo,
addButton
);
formBox.getChildren().add(inputBox);
return formBox;
}
private VBox createTableSection() {
VBox tableBox = new VBox(10);
tableBox.setPadding(new Insets(10));
Label tableLabel = new Label("人員データ");
tableLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
tableView = new TableView<>();
data = FXCollections.observableArrayList();
tableView.setItems(data);
// テーブル列の定義
TableColumn<Person, String> nameCol = new TableColumn<>("名前");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
nameCol.setMinWidth(150);
TableColumn<Person, Integer> ageCol = new TableColumn<>("年齢");
ageCol.setCellValueFactory(new PropertyValueFactory<>("age"));
ageCol.setMinWidth(80);
TableColumn<Person, String> cityCol = new TableColumn<>("都市");
cityCol.setCellValueFactory(new PropertyValueFactory<>("city"));
cityCol.setMinWidth(120);
// アクション列
TableColumn<Person, Void> actionCol = new TableColumn<>("操作");
actionCol.setCellFactory(param -> new TableCell<Person, Void>() {
private final Button deleteBtn = new Button("削除");
{
deleteBtn.setOnAction(event -> {
Person person = getTableView().getItems().get(getIndex());
data.remove(person);
updateChart();
});
deleteBtn.setStyle("-fx-background-color: #ff6b6b; -fx-text-fill: white;");
}
@Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(deleteBtn);
}
}
});
actionCol.setMinWidth(80);
tableView.getColumns().addAll(nameCol, ageCol, cityCol, actionCol);
// 検索機能
TextField searchField = new TextField();
searchField.setPromptText("検索...");
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
filterTable(newValue);
});
tableBox.getChildren().addAll(tableLabel, searchField, tableView);
return tableBox;
}
private VBox createChartSection() {
VBox chartBox = new VBox(10);
chartBox.setPadding(new Insets(10));
chartBox.setPrefWidth(300);
Label chartLabel = new Label("都市別分布");
chartLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
pieChart = new PieChart();
pieChart.setTitle("都市別人員分布");
chartBox.getChildren().addAll(chartLabel, pieChart);
return chartBox;
}
private void updateChart() {
// 都市別の人数をカウント
java.util.Map<String, Long> cityCount = data.stream()
.collect(java.util.stream.Collectors.groupingBy(
Person::getCity,
java.util.stream.Collectors.counting()
));
// チャートデータを更新
ObservableList<PieChart.Data> chartData = FXCollections.observableArrayList();
cityCount.forEach((city, count) -> {
chartData.add(new PieChart.Data(city, count));
});
pieChart.setData(chartData);
}
private void filterTable(String searchText) {
if (searchText == null || searchText.isEmpty()) {
tableView.setItems(data);
} else {
ObservableList<Person> filteredData = data.filtered(person ->
person.getName().toLowerCase().contains(searchText.toLowerCase()) ||
person.getCity().toLowerCase().contains(searchText.toLowerCase())
);
tableView.setItems(filteredData);
}
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.showAndWait();
}
public static void main(String[] args) {
launch(args);
}
// Personクラス
public static class Person {
private final SimpleStringProperty name;
private final SimpleIntegerProperty age;
private final SimpleStringProperty city;
public Person(String name, int age, String city) {
this.name = new SimpleStringProperty(name);
this.age = new SimpleIntegerProperty(age);
this.city = new SimpleStringProperty(city);
}
public String getName() { return name.get(); }
public void setName(String value) { name.set(value); }
public SimpleStringProperty nameProperty() { return name; }
public int getAge() { return age.get(); }
public void setAge(int value) { age.set(value); }
public SimpleIntegerProperty ageProperty() { return age; }
public String getCity() { return city.get(); }
public void setCity(String value) { city.set(value); }
public SimpleStringProperty cityProperty() { return city; }
}
}
CSSスタイリング
/* application.css */
.root {
-fx-background-color: #f5f5f5;
-fx-font-family: "Yu Gothic UI", "Meiryo UI", sans-serif;
}
.button {
-fx-background-color: #3498db;
-fx-text-fill: white;
-fx-background-radius: 5px;
-fx-padding: 8px 16px;
-fx-font-size: 14px;
}
.button:hover {
-fx-background-color: #2980b9;
}
.button:pressed {
-fx-background-color: #21618c;
}
.text-field {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 3px;
-fx-padding: 5px;
}
.text-field:focused {
-fx-border-color: #3498db;
-fx-border-width: 2px;
}
.table-view {
-fx-background-color: white;
-fx-table-cell-border-color: transparent;
}
.table-view .column-header {
-fx-background-color: #ecf0f1;
-fx-font-weight: bold;
}
.table-row-cell:selected {
-fx-background-color: #3498db;
-fx-text-fill: white;
}
.table-row-cell:hover {
-fx-background-color: #ebf3fd;
}
.chart-pie {
-fx-border-color: #95a5a6;
-fx-border-width: 1px;
}
.chart-title {
-fx-font-size: 16px;
-fx-font-weight: bold;
}
カスタムコントロールの作成
// CustomSlider.java
package com.example.custom;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
public class CustomSlider extends VBox {
private final DoubleProperty value = new SimpleDoubleProperty();
private final Label titleLabel;
private final Label valueLabel;
private final Slider slider;
public CustomSlider(String title, double min, double max, double initial) {
this.setPadding(new Insets(10));
this.setSpacing(5);
// タイトルラベル
titleLabel = new Label(title);
titleLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;");
// 値表示ラベル
valueLabel = new Label(String.format("%.2f", initial));
valueLabel.setStyle("-fx-font-size: 12px; -fx-text-fill: #666;");
// スライダー
slider = new Slider(min, max, initial);
slider.setShowTickLabels(true);
slider.setShowTickMarks(true);
slider.setMajorTickUnit((max - min) / 4);
// バインディング設定
value.bind(slider.valueProperty());
valueLabel.textProperty().bind(
slider.valueProperty().asString("%.2f")
);
this.getChildren().addAll(titleLabel, valueLabel, slider);
// スタイル設定
this.setStyle("-fx-background-color: white; " +
"-fx-border-color: #ddd; " +
"-fx-border-radius: 5px; " +
"-fx-background-radius: 5px;");
}
public double getValue() {
return value.get();
}
public void setValue(double value) {
slider.setValue(value);
}
public DoubleProperty valueProperty() {
return value;
}
public Slider getSlider() {
return slider;
}
}
アニメーションとトランジション
// AnimationDemo.java
package com.example.animation;
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AnimationDemo extends Application {
private Circle animatedCircle;
private Rectangle animatedRectangle;
private Timeline timeline;
@Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
root.setPadding(new Insets(20));
// アニメーション対象のシェイプ
Pane animationPane = createAnimationPane();
// コントロールパネル
VBox controlPanel = createControlPanel();
root.setCenter(animationPane);
root.setBottom(controlPanel);
Scene scene = new Scene(root, 800, 600);
primaryStage.setTitle("JavaFX アニメーションデモ");
primaryStage.setScene(scene);
primaryStage.show();
}
private Pane createAnimationPane() {
Pane pane = new Pane();
pane.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc;");
pane.setPrefSize(700, 400);
// アニメーション用の円
animatedCircle = new Circle(50, 50, 30);
animatedCircle.setFill(Color.BLUE);
// アニメーション用の矩形
animatedRectangle = new Rectangle(100, 100, 60, 40);
animatedRectangle.setFill(Color.RED);
pane.getChildren().addAll(animatedCircle, animatedRectangle);
return pane;
}
private VBox createControlPanel() {
VBox controlBox = new VBox(10);
controlBox.setPadding(new Insets(10));
HBox buttonBox = new HBox(10);
Button moveButton = new Button("移動アニメーション");
moveButton.setOnAction(e -> startMoveAnimation());
Button rotateButton = new Button("回転アニメーション");
rotateButton.setOnAction(e -> startRotateAnimation());
Button scaleButton = new Button("スケールアニメーション");
scaleButton.setOnAction(e -> startScaleAnimation());
Button colorButton = new Button("色変更アニメーション");
colorButton.setOnAction(e -> startColorAnimation());
Button complexButton = new Button("複合アニメーション");
complexButton.setOnAction(e -> startComplexAnimation());
Button stopButton = new Button("停止");
stopButton.setOnAction(e -> stopAnimation());
buttonBox.getChildren().addAll(
moveButton, rotateButton, scaleButton,
colorButton, complexButton, stopButton
);
controlBox.getChildren().add(buttonBox);
return controlBox;
}
private void startMoveAnimation() {
stopAnimation();
TranslateTransition moveTransition = new TranslateTransition(Duration.seconds(2), animatedCircle);
moveTransition.setFromX(0);
moveTransition.setToX(300);
moveTransition.setFromY(0);
moveTransition.setToY(200);
moveTransition.setCycleCount(Timeline.INDEFINITE);
moveTransition.setAutoReverse(true);
moveTransition.play();
}
private void startRotateAnimation() {
stopAnimation();
RotateTransition rotateTransition = new RotateTransition(Duration.seconds(3), animatedRectangle);
rotateTransition.setFromAngle(0);
rotateTransition.setToAngle(360);
rotateTransition.setCycleCount(Timeline.INDEFINITE);
rotateTransition.play();
}
private void startScaleAnimation() {
stopAnimation();
ScaleTransition scaleTransition = new ScaleTransition(Duration.seconds(1.5), animatedCircle);
scaleTransition.setFromX(1.0);
scaleTransition.setToX(2.0);
scaleTransition.setFromY(1.0);
scaleTransition.setToY(2.0);
scaleTransition.setCycleCount(Timeline.INDEFINITE);
scaleTransition.setAutoReverse(true);
scaleTransition.play();
}
private void startColorAnimation() {
stopAnimation();
timeline = new Timeline();
KeyFrame keyFrame1 = new KeyFrame(Duration.ZERO,
new KeyValue(animatedRectangle.fillProperty(), Color.RED)
);
KeyFrame keyFrame2 = new KeyFrame(Duration.seconds(1),
new KeyValue(animatedRectangle.fillProperty(), Color.GREEN)
);
KeyFrame keyFrame3 = new KeyFrame(Duration.seconds(2),
new KeyValue(animatedRectangle.fillProperty(), Color.BLUE)
);
KeyFrame keyFrame4 = new KeyFrame(Duration.seconds(3),
new KeyValue(animatedRectangle.fillProperty(), Color.RED)
);
timeline.getKeyFrames().addAll(keyFrame1, keyFrame2, keyFrame3, keyFrame4);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
private void startComplexAnimation() {
stopAnimation();
// 複数のアニメーションを組み合わせ
TranslateTransition moveTransition = new TranslateTransition(Duration.seconds(4), animatedCircle);
moveTransition.setToX(400);
moveTransition.setToY(250);
RotateTransition rotateTransition = new RotateTransition(Duration.seconds(4), animatedCircle);
rotateTransition.setToAngle(720);
ScaleTransition scaleTransition = new ScaleTransition(Duration.seconds(4), animatedCircle);
scaleTransition.setToX(1.5);
scaleTransition.setToY(1.5);
FadeTransition fadeTransition = new FadeTransition(Duration.seconds(4), animatedCircle);
fadeTransition.setFromValue(1.0);
fadeTransition.setToValue(0.3);
ParallelTransition parallelTransition = new ParallelTransition(
moveTransition, rotateTransition, scaleTransition, fadeTransition
);
parallelTransition.setCycleCount(Timeline.INDEFINITE);
parallelTransition.setAutoReverse(true);
parallelTransition.play();
}
private void stopAnimation() {
// すべてのトランジションを停止
animatedCircle.getTransforms().clear();
animatedRectangle.getTransforms().clear();
if (timeline != null) {
timeline.stop();
}
// 元の状態にリセット
animatedCircle.setTranslateX(0);
animatedCircle.setTranslateY(0);
animatedCircle.setScaleX(1.0);
animatedCircle.setScaleY(1.0);
animatedCircle.setRotate(0);
animatedCircle.setOpacity(1.0);
animatedRectangle.setTranslateX(0);
animatedRectangle.setTranslateY(0);
animatedRectangle.setScaleX(1.0);
animatedRectangle.setScaleY(1.0);
animatedRectangle.setRotate(0);
animatedRectangle.setFill(Color.RED);
}
public static void main(String[] args) {
launch(args);
}
}
Module システムとプロジェクト設定
// module-info.java
module com.example.javafxapp {
requires javafx.controls;
requires javafx.fxml;
requires javafx.web;
requires javafx.media;
// FXMLController用にパッケージを開放
opens com.example to javafx.fxml;
opens com.example.controllers to javafx.fxml;
// メインクラスをエクスポート
exports com.example;
}
<!-- pom.xml (Maven) -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>javafx-app</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<javafx.version>21.0.1</javafx.version>
<javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
</properties>
<dependencies>
<!-- JavaFX Controls -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- JavaFX FXML -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- JavaFX Web (オプション) -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- JavaFX Media (オプション) -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
<!-- JavaFX Maven Plugin -->
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>${javafx.maven.plugin.version}</version>
<configuration>
<mainClass>com.example.MainApp</mainClass>
</configuration>
</plugin>
<!-- JLink Plugin for creating custom runtime images -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jlink-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<addModules>java.base,javafx.controls,javafx.fxml</addModules>
<launcher>app=com.example.javafxapp/com.example.MainApp</launcher>
</configuration>
</plugin>
</plugins>
</build>
</project>
ビルドとデプロイメント
# Maven を使用したプロジェクトの実行
mvn clean compile
mvn javafx:run
# JARファイルの作成
mvn clean package
# カスタムランタイムイメージの作成
mvn clean javafx:jlink
# 実行可能JARファイルの実行 (JavaFXが含まれている場合)
java -jar target/javafx-app-1.0-SNAPSHOT.jar
# モジュールパスを指定した実行
java --module-path "lib" --add-modules javafx.controls,javafx.fxml -cp target/classes com.example.MainApp
# JPackage を使用したネイティブパッケージの作成 (JDK 17+)
jpackage --input target/classes \
--name "JavaFX App" \
--main-jar javafx-app-1.0-SNAPSHOT.jar \
--main-class com.example.MainApp \
--type dmg \
--java-options '--enable-preview'
プラットフォーム別の特別な考慮事項
Windows
- Windows 10以降を推奨
- JDK 21 LTS の使用を推奨
- ダークモードサポートの考慮
- MSIインストーラーでの配布が可能
macOS
- macOS 10.15 (Catalina) 以降をサポート
- Apple Siliconネイティブサポート
- Notarization対応が必要(App Store配布時)
- DMGファイルでの配布が一般的
Linux
- OpenJDK + OpenJFXの組み合わせを推奨
- GTK依存関係の管理が必要
- AppImageやSnapパッケージでの配布
- 複数のLinuxディストリビューションでのテスト推奨