JavaFX

Modern desktop application framework for Java. Provides rich UI, 2D/3D graphics, and media playback capabilities. Supports MVC architecture with FXML and CSS styling. Positioned as Swing's successor.

DesktopJavaGUICross-platformScene GraphFXML

GitHub Overview

openjdk/jfx

JavaFX mainline development

Stars3,018
Watchers88
Forks525
Created:May 6, 2019
Language:C++
License:GNU General Public License v2.0

Topics

javajavafxopenjdkopenjfx

Star History

openjdk/jfx Star History
Data as of: 8/13/2025, 01:43 AM

Framework

JavaFX

Overview

JavaFX is a modern GUI toolkit for developing rich desktop applications on the Java platform. Developed as part of the OpenJDK project, it provides high-performance graphics, media processing, web content embedding, and rich UI controls.

Details

As of 2025, JavaFX is provided as a stable GUI framework based on OpenJDK 21. Positioned as the successor to Swing and AWT, it comprehensively provides the functionality needed for modern desktop application development.

Key features of JavaFX:

  • Scene Graph Architecture: Efficient UI management through hierarchical scene graphs
  • CSS Styling: Web-standard CSS styling capabilities
  • FXML: XML-based declarative UI definition
  • Modern Controls: Rich UI controls and charts
  • Hardware Acceleration: High-speed rendering with GPU acceleration
  • Media Support: Audio/video playback and media processing

As of 2025, many enterprise-level applications and tools are developed with JavaFX, establishing its position as the standard desktop UI framework in the Java ecosystem.

Advantages & Disadvantages

Advantages

  • Powerful Java Ecosystem: Leverage Java's rich libraries and tools
  • Cross-platform: Runs on Windows, macOS, and Linux
  • Declarative UI: Maintainable UI design with FXML
  • CSS Styling: Familiar styling approach for web developers
  • High-performance Graphics: Smooth animations with GPU acceleration
  • Rich Controls: Diverse UI components including charts, tables, and media players
  • Scene Builder: Visual UI design tool
  • Enterprise Support: Long-term support as an OpenJDK project

Disadvantages

  • Startup Time: Application startup delay due to JVM initialization
  • Memory Usage: Larger memory footprint compared to native apps
  • Distribution Size: Large distribution packages including JRE
  • Modern UI: Lag in adapting to latest UI design trends
  • Learning Curve: Need to understand Scene Graph, FXML, and CSS
  • Platform-specific Features: Limited access to OS-specific functionality

Key Links

Code Examples

Basic JavaFX Application

// 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) {
        // Create UI components
        Label label = new Label("Hello, JavaFX World!");
        
        // Create layout container
        StackPane root = new StackPane();
        root.getChildren().add(label);
        
        // Create scene
        Scene scene = new Scene(root, 400, 300);
        
        // Configure stage
        primaryStage.setTitle("JavaFX Hello World");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

MVC Architecture using FXML

<!-- 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="Name:" />
            <TextField fx:id="nameField" promptText="Enter your name" />
            <Button fx:id="greetButton" text="Greet" onAction="#handleGreetAction" />
         </children>
      </HBox>
      
      <TextArea fx:id="outputArea" prefRowCount="10" editable="false" />
      
      <HBox spacing="10" alignment="CENTER">
         <children>
            <Button text="Clear" onAction="#handleClearAction" />
            <Button text="Exit" 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) {
        // Initialization logic
        outputArea.appendText("Application started.\n");
        
        // Disable button when text field is empty
        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] Hello, %s!\n", 
                timestamp, name));
            nameField.clear();
        }
    }
    
    @FXML
    private void handleClearAction() {
        outputArea.clear();
        outputArea.appendText("Log cleared.\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);
        
        // Apply CSS stylesheet
        scene.getStylesheets().add(
            getClass().getResource("/css/application.css").toExternalForm()
        );
        
        primaryStage.setTitle("JavaFX FXML Application");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

Advanced UI Component Implementation

// 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) {
        // Main layout
        BorderPane root = new BorderPane();
        root.setPadding(new Insets(10));
        
        // Top: Form
        VBox topSection = createFormSection();
        
        // Center: Table
        VBox centerSection = createTableSection();
        
        // Right: Chart
        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("Advanced JavaFX Application");
        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("Name");
        
        Spinner<Integer> ageSpinner = new Spinner<>(1, 100, 25);
        ageSpinner.setEditable(true);
        
        ComboBox<String> cityCombo = new ComboBox<>();
        cityCombo.setItems(FXCollections.observableArrayList(
            "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia"
        ));
        cityCombo.setPromptText("Select City");
        
        Button addButton = new Button("Add");
        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();
                
                // Clear form
                nameField.clear();
                ageSpinner.getValueFactory().setValue(25);
                cityCombo.setValue(null);
            } else {
                showAlert("Error", "Please fill all fields.");
            }
        });
        
        inputBox.getChildren().addAll(
            new Label("Name:"), nameField,
            new Label("Age:"), ageSpinner,
            new Label("City:"), 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("Personnel Data");
        tableLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
        
        tableView = new TableView<>();
        data = FXCollections.observableArrayList();
        tableView.setItems(data);
        
        // Define table columns
        TableColumn<Person, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
        nameCol.setMinWidth(150);
        
        TableColumn<Person, Integer> ageCol = new TableColumn<>("Age");
        ageCol.setCellValueFactory(new PropertyValueFactory<>("age"));
        ageCol.setMinWidth(80);
        
        TableColumn<Person, String> cityCol = new TableColumn<>("City");
        cityCol.setCellValueFactory(new PropertyValueFactory<>("city"));
        cityCol.setMinWidth(120);
        
        // Action column
        TableColumn<Person, Void> actionCol = new TableColumn<>("Actions");
        actionCol.setCellFactory(param -> new TableCell<Person, Void>() {
            private final Button deleteBtn = new Button("Delete");
            
            {
                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);
        
        // Search functionality
        TextField searchField = new TextField();
        searchField.setPromptText("Search...");
        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("City Distribution");
        chartLabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
        
        pieChart = new PieChart();
        pieChart.setTitle("Personnel Distribution by City");
        
        chartBox.getChildren().addAll(chartLabel, pieChart);
        return chartBox;
    }
    
    private void updateChart() {
        // Count people by city
        java.util.Map<String, Long> cityCount = data.stream()
            .collect(java.util.stream.Collectors.groupingBy(
                Person::getCity,
                java.util.stream.Collectors.counting()
            ));
        
        // Update chart data
        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 class
    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 Styling

/* application.css */
.root {
    -fx-background-color: #f5f5f5;
    -fx-font-family: "Segoe UI", "San Francisco", "Helvetica Neue", 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;
}

Custom Control Creation

// 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);
        
        // Title label
        titleLabel = new Label(title);
        titleLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;");
        
        // Value display label
        valueLabel = new Label(String.format("%.2f", initial));
        valueLabel.setStyle("-fx-font-size: 12px; -fx-text-fill: #666;");
        
        // Slider
        slider = new Slider(min, max, initial);
        slider.setShowTickLabels(true);
        slider.setShowTickMarks(true);
        slider.setMajorTickUnit((max - min) / 4);
        
        // Binding setup
        value.bind(slider.valueProperty());
        valueLabel.textProperty().bind(
            slider.valueProperty().asString("%.2f")
        );
        
        this.getChildren().addAll(titleLabel, valueLabel, slider);
        
        // Style settings
        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;
    }
}

Animation and Transitions

// 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));
        
        // Animation target shapes
        Pane animationPane = createAnimationPane();
        
        // Control panel
        VBox controlPanel = createControlPanel();
        
        root.setCenter(animationPane);
        root.setBottom(controlPanel);
        
        Scene scene = new Scene(root, 800, 600);
        
        primaryStage.setTitle("JavaFX Animation Demo");
        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);
        
        // Animation circle
        animatedCircle = new Circle(50, 50, 30);
        animatedCircle.setFill(Color.BLUE);
        
        // Animation rectangle
        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("Move Animation");
        moveButton.setOnAction(e -> startMoveAnimation());
        
        Button rotateButton = new Button("Rotate Animation");
        rotateButton.setOnAction(e -> startRotateAnimation());
        
        Button scaleButton = new Button("Scale Animation");
        scaleButton.setOnAction(e -> startScaleAnimation());
        
        Button colorButton = new Button("Color Animation");
        colorButton.setOnAction(e -> startColorAnimation());
        
        Button complexButton = new Button("Complex Animation");
        complexButton.setOnAction(e -> startComplexAnimation());
        
        Button stopButton = new Button("Stop");
        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();
        
        // Combine multiple animations
        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() {
        // Stop all transitions
        animatedCircle.getTransforms().clear();
        animatedRectangle.getTransforms().clear();
        
        if (timeline != null) {
            timeline.stop();
        }
        
        // Reset to original state
        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 System and Project Configuration

// module-info.java
module com.example.javafxapp {
    requires javafx.controls;
    requires javafx.fxml;
    requires javafx.web;
    requires javafx.media;
    
    // Open packages for FXML Controller
    opens com.example to javafx.fxml;
    opens com.example.controllers to javafx.fxml;
    
    // Export main class
    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 (Optional) -->
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-web</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        
        <!-- JavaFX Media (Optional) -->
        <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>

Build and Deployment

# Run project using Maven
mvn clean compile
mvn javafx:run

# Create JAR file
mvn clean package

# Create custom runtime image
mvn clean javafx:jlink

# Run executable JAR (if JavaFX is included)
java -jar target/javafx-app-1.0-SNAPSHOT.jar

# Run with module path specified
java --module-path "lib" --add-modules javafx.controls,javafx.fxml -cp target/classes com.example.MainApp

# Create native package using 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'

Platform-specific Considerations

Windows

  • Windows 10 or later recommended
  • Recommend using JDK 21 LTS
  • Consider dark mode support
  • MSI installer distribution available

macOS

  • Supports macOS 10.15 (Catalina) and later
  • Apple Silicon native support
  • Notarization required (for App Store distribution)
  • DMG file distribution is common

Linux

  • Recommend OpenJDK + OpenJFX combination
  • GTK dependency management required
  • AppImage or Snap package distribution
  • Testing on multiple Linux distributions recommended