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.
GitHub Overview
Topics
Star History
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
- JavaFX Official Site
- OpenJFX Documentation
- JavaFX GitHub Repository
- Scene Builder
- JavaFX Tutorials
- OpenJDK Official Site
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