Java Module 18 – Desktop GUIs with JavaFX (and Swing for legacy code)
JavaFX setup and your first window
Get JavaFX 27 apart from the JDK, open a first window from the SDK, a compact source file and Maven, and follow each step of the Application life cycle.
What you will learn
- Explain what JavaFX is, how OpenJFX ships it apart from the JDK and which version to choose
- Run a first JavaFX program from the SDK, from a compact source file and with Maven
- Describe the Application life cycle and the Stage, Scene and scene-graph structure
Before you start
What you’ll learn · Before you start
What you will learn
- Explain what JavaFX is, how OpenJFX ships it apart from the JDK and which version to choose
- Run a first JavaFX program from the SDK, from a compact source file and with Maven
- Describe the Application life cycle and the Stage, Scene and scene-graph structure
Before you start
On this page
JavaFX is Java’s toolkit for desktop applications: windows, buttons, text fields, lists, charts, animation and
media, drawn by its own graphics engine on Windows, macOS and Linux. It is not part of the JDK. The OpenJFX project
builds it, and JavaFX ships on its own: as an SDK that you unpack, as jmods for building runtime images and as
libraries on Maven Central. A JavaFX program therefore needs two things, a JDK and JavaFX on the module path. With
JDK 25, use JavaFX 27. A program starts from a class that extends Application, whose start method receives a
Stage, the window, and gives it a Scene, the content.
This lesson opens one small window three ways, from the SDK, as a compact source file and with Maven, on Eclipse Temurin 25.0.4.1 with JavaFX 27, and records what each step prints, including the errors that stop most first attempts.
Where JavaFX comes from, and which version to take
Gluon, whose engineers lead the OpenJFX project, publishes free builds of every release for Windows, Linux and macOS:
the SDK (a lib folder of modular JARs and native libraries), the jmods and the Maven artifacts. JavaFX is licensed
under the GPL version 2 with the Classpath Exception, like OpenJDK; the exception lets you link it with your own code
and distribute the result under terms of your choice. A feature release comes every six months, and some releases get
long-term support:
| JavaFX | Runs on | Long-term support | Take it when |
|---|---|---|---|
| 27 | JDK 25 or later | No | You start new work on JDK 25: it is the newest release |
| 26 | JDK 24 or later | No | Never for new work: 27 replaces it |
| 25 | JDK 23 or later | Yes | Your team accepts only long-term-support releases |
| 21 | JDK 17 or later | Yes | An application still runs on JDK 17 |
| 17 | JDK 11 or later | Yes, the oldest line still updated | Only to keep an old application alive while you move it |
Gluon recommends the newest release or the newest patch of a supported long-term line, because the others no longer
receive fixes. The minimum JDK is strict. JavaFX 27 is compiled with --release 25, and on JDK 21 it fails before
your main method runs. Each download comes with a SHA-256 sum on Gluon’s page, worth comparing before you unpack it:
from session import jdk_21, run, sdk
sdk("27")
sdk("25.0.4")
jdk_21()
run("""
jdk-21/bin/java -version
jdk-21/bin/java --module-path javafx-sdk-27/lib --add-modules javafx.controls Peek.java Welcome
javac --module-path javafx-sdk-25.0.4/lib --add-modules javafx.controls -d classes-25 Welcome.java
ls classes-25
""", fails=(
"jdk-21/bin/java --module-path javafx-sdk-27/lib --add-modules javafx.controls Peek.java Welcome",
)) Output
$ jdk-21/bin/java -version openjdk version "21.0.12.1" LTS OpenJDK Runtime Environment Temurin-21.0.12.1+1 (build 21.0.12.1+1-LTS) OpenJDK 64-Bit Server VM Temurin-21.0.12.1+1 (build 21.0.12.1+1-LTS, mixed mode, sharing) $ jdk-21/bin/java --module-path javafx-sdk-27/lib --add-modules javafx.controls Peek.java Welcome Error occurred during initialization of boot layer java.lang.module.FindException: Error reading module: javafx-sdk-27/lib/javafx.fxml.jar Caused by: java.lang.module.InvalidModuleDescriptorException: Unsupported major.minor version 69.0 [exit status 1] $ javac --module-path javafx-sdk-25.0.4/lib --add-modules javafx.controls -d classes-25 Welcome.java $ ls classes-25 Welcome.class
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 versions_session.py
The message names javafx.fxml.jar, the first module the JVM read, but every module of the SDK has the same
problem: class-file version 69 is Java 25, and a Java 21 runtime reads class files up to version 65. The last two
commands show the other side. The same Welcome.java compiled against the JavaFX 25.0.4 SDK without a change, so code
written for 27 moves to the long-term line, or back, without edits as long as it avoids the newest APIs.
A first window
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/** A first JavaFX program: one window that says "Welcome to Java". */
public class Welcome extends Application {
@Override
public void start(Stage stage) {
Label greeting = new Label("Welcome to Java");
StackPane root = new StackPane(greeting); // the root of the scene graph
Scene scene = new Scene(root, 320, 160); // the window's content and its size
stage.setTitle("Welcome");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
} Four names carry the whole program:
Applicationis the class every JavaFX program extends. Its abstract methodstart(Stage)is where the window is built;mainonly callslaunch(args), which starts the JavaFX runtime and then your application.Stageis a window. The runtime creates the first one, the primary stage, and passes it tostart.Sceneis the content of a stage and its size: here 320 by 160 pixels around one root node.- The scene graph is the tree of nodes under that root.
StackPaneis a pane, a node that holds and arranges other nodes, and theLabelis its only child.
The window of practical 1 as JavaFX sees it, from the Stage down to each node
Text description of the diagram
The diagram shows three boxes nested inside each other, for the window that the lesson's exercise builds.
- The outer box is the Stage, the window itself, with the title "Practical 1".
- Inside it is the Scene, the content of the window, 320 pixels wide and 200 high.
- Inside the Scene is the root node, a VBox, which is a Parent: a node that holds other nodes.
An arrow labelled getChildren() leads from the VBox to its four children, listed from top to bottom in the order the VBox shows them: a Label with the text "Welcome to Java", a Label with "Learning Java Now", a Label with "Programming is fun", and a Button labelled "Quit".
Together the VBox and its children form the scene graph: a tree with one root, in which every node has at most one parent.
Every node in the tree has at most one parent, and a Parent such as StackPane or VBox lists its children with
getChildren(). Panes decide where their children go; the next lesson of this module compares them. Controls such as
Label and Button are nodes too, so a window is nothing more than a tree of objects that you build in start.
Running it from the SDK
Unpack the SDK, here into javafx-sdk-27 next to the program, and give java three options: where the JavaFX modules
are, which module to add, and permission for JavaFX’s native code. The session below first leaves them out one at a
time:
from session import run, sdk
sdk("27")
run("""
ls javafx-sdk-27/lib/*.jar
java Welcome.java
java --module-path javafx-sdk-27/lib Welcome.java
java --module-path javafx-sdk-27/lib --add-modules javafx.controls Peek.java Welcome
java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics Peek.java Welcome
""", fails=(
"java Welcome.java",
"java --module-path javafx-sdk-27/lib Welcome.java",
), first={
"java Welcome.java": 6,
"java --module-path javafx-sdk-27/lib Welcome.java": 6,
}) Output
$ ls javafx-sdk-27/lib/*.jar
javafx-sdk-27/lib/javafx-swt.jar
javafx-sdk-27/lib/javafx.base.jar
javafx-sdk-27/lib/javafx.controls.jar
javafx-sdk-27/lib/javafx.fxml.jar
javafx-sdk-27/lib/javafx.graphics.jar
javafx-sdk-27/lib/javafx.media.jar
javafx-sdk-27/lib/javafx.swing.jar
javafx-sdk-27/lib/javafx.web.jar
javafx-sdk-27/lib/jdk.jsobject.jar
javafx-sdk-27/lib/jfx.incubator.input.jar
javafx-sdk-27/lib/jfx.incubator.richtext.jar
$ java Welcome.java
Welcome.java:1: error: package javafx.application does not exist
import javafx.application.Application;
^
Welcome.java:2: error: package javafx.scene does not exist
import javafx.scene.Scene;
^
[… 56 more lines]
15 errors
error: compilation failed
[exit status 1]
$ java --module-path javafx-sdk-27/lib Welcome.java
Welcome.java:1: error: package javafx.application is not visible
import javafx.application.Application;
^
(package javafx.application is declared in module javafx.graphics, which is not in the module graph)
Welcome.java:2: error: package javafx.scene is not visible
import javafx.scene.Scene;
[… 22 more lines]
7 errors
error: compilation failed
[exit status 1]
$ java --module-path javafx-sdk-27/lib --add-modules javafx.controls Peek.java Welcome
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by com.sun.glass.utils.NativeLibLoader in module javafx.graphics (file:javafx-sdk-27/lib/javafx.graphics.jar)
WARNING: Use --enable-native-access=javafx.graphics to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
Stage "Welcome", scene 320 x 160
StackPane
Label: Welcome to Java
launch() returned: the application ended
$ java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics Peek.java Welcome
Stage "Welcome", scene 320 x 160
StackPane
Label: Welcome to Java
launch() returned: the application ended
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sdk_session.py
- Without a module path, javac knows nothing about JavaFX: “package javafx.application does not exist”, for every import and every use, 15 errors in all.
--module-path javafx-sdk-27/libsays where the modules are, but a program on the class path reads none of them until one is added to the module graph, so 7 errors remain: each package “is not visible”.--add-modules javafx.controlsadds the module, together withjavafx.graphicsandjavafx.base, which it requires.- JavaFX loads native libraries for its windowing and graphics. Since JDK 24 the JVM warns about such code (JEP 472)
unless the module has been allowed it, which is what
--enable-native-access=javafx.graphicsdoes.
The window itself printed nothing, so this page records it through Peek.java, a small helper next to the lesson’s
examples. It launches the application named after it, prints the scene graph of the first window, and ends the
application with Platform.exit(). java Peek.java Welcome works because the source launcher compiles the other
source files of a folder on demand (JEP 458).
Note
The recordings on this page ran on a computer without a screen, with JavaFX’s headless platform and software
renderer (-Dglass.platform=headless -Dprism.order=sw, given through the JDK_JAVA_OPTIONS environment variable).
On your computer, leave them out: java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics Welcome.java opens the window, which stays until you close it.
The life cycle of an application
launch does more than open a window. This program prints which thread runs each step, then ends itself so that the
last step runs too:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
/** Prints which thread runs each step of an application's life, then ends itself so that stop() runs too. */
public class LifeCycle extends Application {
static void log(String step) {
System.out.println(step + " on " + Thread.currentThread().getName());
}
public LifeCycle() {
log("constructor");
}
@Override
public void init() {
log("init()");
}
@Override
public void start(Stage stage) {
log("start()");
stage.setScene(new Scene(new Label("Watch the console"), 260, 80));
stage.show();
Platform.runLater(Platform::exit); // end at once instead of waiting for the window to close
}
@Override
public void stop() {
log("stop()");
}
public static void main(String[] args) {
log("main()");
launch(args);
log("launch() returned");
}
} from session import run, sdk
sdk("27")
run("""
java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics LifeCycle.java
""") Output
$ java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics LifeCycle.java main() on main constructor on JavaFX Application Thread init() on JavaFX-Launcher start() on JavaFX Application Thread stop() on JavaFX Application Thread launch() returned on main
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lifecycle_session.py
The runtime constructed the application and called start on the JavaFX Application Thread, the one thread that
may touch windows and nodes once they are shown. init() ran in between, on the JavaFX-Launcher thread, so the
documentation forbids creating a Stage or a Scene there; it is the place to read settings. After
Platform.exit(), stop() ran on the application thread, and only then did launch return to main. An
application also ends when its last window closes, as long as Platform.setImplicitExit(false) has not been called.
Platform.exit() is the documented way to end one yourself; System.exit skips stop().
A class without main
The JDK’s java launcher knows about JavaFX: it can start an Application subclass that has no main method.
Source-file mode cannot:
from session import run, sdk
sdk("27")
run("""
java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics nomain/NoMain.java
javac --module-path javafx-sdk-27/lib --add-modules javafx.controls -d nomain/classes nomain/NoMain.java
java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics -cp nomain/classes NoMain
""", fails=(
"java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics nomain/NoMain.java",
)) Output
$ java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics nomain/NoMain.java error: can't find main(String[]) or main() method in class: NoMain [exit status 1] $ javac --module-path javafx-sdk-27/lib --add-modules javafx.controls -d nomain/classes nomain/NoMain.java $ java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics -cp nomain/classes NoMain start() ran, although NoMain has no main method
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 nomain_session.py
The same class ran once it was compiled with javac, but java NoMain.java refused it. A main that calls
launch(args) costs one line and works in every mode and every IDE, so every program in this module keeps one.
A compact source file
Compact source files (JEP 512) and module imports (JEP 511) make a short JavaFX program even shorter:
import module javafx.controls; imports every package of javafx.controls and of the modules it requires, so the
list of imports shrinks to one line. Two details trip people up, and the first is a compile error:
import module javafx.controls;
import javafx.scene.control.Label; // javafx.controls and java.base both have a Label: this one wins
public static class App extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("Welcome");
stage.setScene(new Scene(new StackPane(new Label("Welcome to Java")), 320, 160));
stage.show();
}
}
void main() {
Application.launch(App.class);
} from session import run, sdk
sdk("27")
run("""
java --module-path javafx-sdk-27/lib --add-modules javafx.controls compact/CompactClash.java
javac --module-path javafx-sdk-27/lib --add-modules javafx.controls -d compact/classes compact/WelcomeCompact.java
ls compact/classes
java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics -cp compact/classes Peek.java 'WelcomeCompact$App'
""", fails=(
"java --module-path javafx-sdk-27/lib --add-modules javafx.controls compact/CompactClash.java",
)) Output
$ java --module-path javafx-sdk-27/lib --add-modules javafx.controls compact/CompactClash.java
compact/CompactClash.java:7: error: reference to Label is ambiguous
stage.setScene(new Scene(new StackPane(new Label("Welcome to Java")), 320, 160));
^
both class javafx.scene.control.Label in javafx.scene.control and interface java.lang.classfile.Label in java.lang.classfile match
1 error
error: compilation failed
[exit status 1]
$ javac --module-path javafx-sdk-27/lib --add-modules javafx.controls -d compact/classes compact/WelcomeCompact.java
$ ls compact/classes
WelcomeCompact.class
WelcomeCompact$App.class
$ java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics -cp compact/classes Peek.java 'WelcomeCompact$App'
Stage "Welcome", scene 320 x 160
StackPane
Label: Welcome to Java
launch() returned: the application ended
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 compact_session.py
- The Label clash. A compact source file also imports the module
java.base, which contains the class-file API and its interfacejava.lang.classfile.Label. Both imports offer aLabel, so javac stops with “reference to Label is ambiguous”. A single-type import,import javafx.scene.control.Label;, wins over every module import. - No
extends. The class a compact source file declares for you extendsObject, so it cannot be theApplication. The file declarespublic static class App extends Applicationinside it and launches that class frommain.
Compact files suit experiments and one-file exercises. Projects with several classes are easier to grow as ordinary classes in packages, with a build tool.
With Maven
A build tool downloads JavaFX for you, so nothing needs unpacking. The project lists javafx-controls as a dependency
and adds the JavaFX Maven plugin, whose javafx:run goal builds the command line of the previous sections:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>welcome-fx</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>27</javafx.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.5.0</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.16.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.example.welcome.LifeCycle</mainClass>
<options>
<option>--enable-native-access=javafx.graphics</option>
</options>
</configuration>
</plugin>
</plugins>
</build>
</project> from session import run
run("""
cd welcome-fx
mvn --batch-mode --no-transfer-progress javafx:run
""", maven=True) Output
$ cd welcome-fx $ mvn --batch-mode --no-transfer-progress javafx:run [INFO] Scanning for projects... [INFO] Loaded 24065 auto-discovered prefixes for remote repository central (prefixes-central.txt) [INFO] [INFO] -----------------------< com.example:welcome-fx >----------------------- [INFO] Building welcome-fx 1.0.0 [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] >>> javafx:0.0.8:run (default-cli) > process-classes @ welcome-fx >>> [INFO] [INFO] --- resources:3.5.0:resources (default-resources) @ welcome-fx --- [INFO] skip non existing resourceDirectory welcome-fx/src/main/resources [INFO] [INFO] --- compiler:3.16.0:compile (default-compile) @ welcome-fx --- [INFO] Loaded 74 auto-discovered prefixes for remote repository apache.snapshots (prefixes-apache.snapshots.txt) [INFO] Recompiling the module because of changed source code. [INFO] Compiling 1 source file with javac [debug release 25] to target/classes [INFO] [INFO] <<< javafx:0.0.8:run (default-cli) < process-classes @ welcome-fx <<< [INFO] [INFO] [INFO] --- javafx:0.0.8:run (default-cli) @ welcome-fx --- main() on main constructor on JavaFX Application Thread init() on JavaFX-Launcher start() on JavaFX Application Thread stop() on JavaFX Application Thread launch() returned on main [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: … s [INFO] Finished at: … [INFO] ------------------------------------------------------------------------
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 maven_session.py
javafx:run first ran the build up to process-classes, so it compiled the code, then started the program with
JavaFX on the module path and the option from the POM. Maven fetched two files for each JavaFX module: a 306-byte
javafx-controls-27.jar, which holds only a manifest, and javafx-controls-27-mac-aarch64.jar, with the classes and
native code for this computer, because the JavaFX POM picks the platform from the operating system. Version 0.0.8 is
the newest release of the plugin on Maven Central, and it ran JavaFX 27 on Maven 3.10.0 without trouble. Gradle users add the plugin org.openjfx.javafxplugin instead, whose newest version on the Gradle
Plugin Portal is 0.1.0.
Which way to choose
| Criterion | The SDK and the java command | A compact source file | Maven or Gradle |
|---|---|---|---|
| What you set up | Unpack the SDK once | Unpack the SDK once | A POM or build script; JavaFX is downloaded |
| What you type | java with three options | The same command, on one file | mvn javafx:run |
| Grows into a real project | With effort | No: one file only | Yes, with tests, packaging and IDE support |
| Platform-specific files | You pick the SDK for your system | You pick the SDK for your system | Chosen for you by the JavaFX POM |
| When to choose | You want to see exactly what java needs | You try out one idea quickly | You build an application others will use |
GTU 3140705, Object Oriented Programming-I
Gujarat Technological University’s syllabus for this subject teaches JavaFX in units 7 and 8 and in practicals 17 to 20. Practical 1, which prints three lines of text, makes a good first window, so this lesson’s exercise uses it. The module covers the syllabus as follows; MySmartCoPilot is not affiliated with GTU, and the syllabus PDF is the authority.
| Syllabus item | Lesson of this module |
|---|---|
| Unit 7: the basic structure of a JavaFX program | This lesson |
| Unit 7: panes, shapes, the Color and Font classes, Image and ImageView; practical 17 | Panes, shapes, colours, fonts and images |
| Unit 7: property binding and listeners for observable objects | Properties, binding and listeners |
| Unit 7: events, handlers, inner and anonymous classes, mouse and key events; practicals 18 and 19 | Events: handlers, mouse and keys |
| Unit 8: Label to Slider; practical 20 | UI controls |
| Unit 7: animation; unit 8: video and audio | Animation and multimedia |
Your turn: practical 1 as a window
Build the window of the diagram above: the three lines of practical 1 in a VBox, and a Quit button under them
that ends the application. The starter shows the first line; the sample checks launch your class through its
launcher, read the scene graph and press the button:
Java (WelcomeWindow.java) · exercise/WelcomeWindow.java
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/** Practical 1 as a window: three lines of text and a Quit button. */
public class WelcomeWindow extends Application {
@Override
public void start(Stage stage) {
VBox root = new VBox(12, new Label("Welcome to Java"));
// 1. Add the labels "Learning Java Now" and "Programming is fun" under the first one.
// 2. Add a Button labelled "Quit" after them, whose action calls Platform.exit().
root.setPadding(new Insets(16));
stage.setTitle("Practical 1");
stage.setScene(new Scene(root, 320, 200));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
} Java (WelcomeChecks.java) · exercise/WelcomeChecks.java
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/** Sample checks for the practical 1 window: java ... exercise/WelcomeChecks.java */
public class WelcomeChecks {
static int passed;
static int total;
static void check(String what, boolean ok) {
total++;
if (ok) passed++;
System.out.println((ok ? "pass " : "FAIL ") + what);
}
public static void main(String[] args) throws Exception {
CountDownLatch ended = new CountDownLatch(1);
new Thread(() -> {
Application.launch(WelcomeWindow.class);
ended.countDown();
}).start();
Stage stage = firstWindow();
CompletableFuture<Boolean> pressedQuit = new CompletableFuture<>();
Platform.runLater(() -> {
Parent root = stage.getScene().getRoot();
List<Node> nodes = root.getChildrenUnmodifiable();
List<String> labels = nodes.stream().filter(Label.class::isInstance).map(n -> ((Label) n).getText()).toList();
Button quit = nodes.stream().filter(Button.class::isInstance).map(Button.class::cast)
.filter(b -> b.getText().equals("Quit")).findFirst().orElse(null);
check("the root of the scene is a VBox", root instanceof VBox);
check("three labels: Welcome to Java, Learning Java Now, Programming is fun",
labels.equals(List.of("Welcome to Java", "Learning Java Now", "Programming is fun")));
check("a Quit button comes after the labels", quit != null && nodes.indexOf(quit) == nodes.size() - 1);
if (quit != null) quit.fire(); // presses the button, as a click would
pressedQuit.complete(quit != null);
});
boolean quitFound = pressedQuit.get(5, TimeUnit.SECONDS);
boolean exited = quitFound && ended.await(5, TimeUnit.SECONDS);
check("pressing Quit ends the application", exited);
if (!exited) Platform.exit();
System.out.println(passed + " of " + total + " checks passed");
System.exit(passed == total ? 0 : 1);
}
/** Waits until the application shows its first window. */
static Stage firstWindow() throws Exception {
CompletableFuture<Stage> found = new CompletableFuture<>();
while (!found.isDone()) {
try {
Platform.runLater(() -> Window.getWindows().stream()
.filter(w -> w instanceof Stage s && s.isShowing()).findFirst()
.ifPresent(w -> found.complete((Stage) w)));
} catch (IllegalStateException notStartedYet) {
// the JavaFX runtime is not running yet: ask again
}
Thread.sleep(50);
}
return found.get();
}
} from session import run, sdk
sdk("27")
run("""
java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics exercise/WelcomeChecks.java
""", fails=(
"java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics exercise/WelcomeChecks.java",
)) Output
$ java --module-path javafx-sdk-27/lib --add-modules javafx.controls --enable-native-access=javafx.graphics exercise/WelcomeChecks.java pass the root of the scene is a VBox FAIL three labels: Welcome to Java, Learning Java Now, Programming is fun FAIL a Quit button comes after the labels FAIL pressing Quit ends the application 1 of 4 checks passed [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py
Edit WelcomeWindow.java until the same command prints 4 of 4 checks passed:
- Add the labels “Learning Java Now” and “Programming is fun” to the
VBox, after the first one. - Add a
Buttonwith the text “Quit” as the last child, and give it an action withsetOnAction. - In the action, end the application the documented way.
A hint for the last check: the checks wait for launch to return, which happens only when the application ends, so
closing the window with stage.close() works too, but Platform.exit() says what you mean.
Interview questions
Fresher · What are the Stage, the Scene and the scene graph in JavaFX?
A Stage is a window: it has a title, a size and a position, and show() puts it on the screen. The runtime creates
the first one, the primary stage, and passes it to Application.start. A Scene is the content of a stage: it holds
one root node and the size of the area it fills, and a stage shows one scene at a time, so switching screens can mean
setting a new scene. The scene graph is the tree of nodes under that root. Panes such as VBox and BorderPane are
parents that arrange their children, and controls and shapes are nodes too. Every node has at most one parent, which
is why adding a node to a second pane removes it from the first.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A JavaFX desktop tool starts on your laptop, but on a colleague's computer it stops before main() with InvalidModuleDescriptorException: Unsupported major.minor version 69.0. Both use the same JavaFX 27 SDK. What happened, and how do you make the build catch it before anyone runs the tool?
-
Fresher to mid
A JavaFX app reads a 200 MB catalogue file in start() before calling stage.show(), and users see nothing for eight seconds. A teammate suggests moving the loading into init(). Is that enough? Say where each part of the work belongs, and why.
Key takeaways
- JavaFX ships apart from the JDK, as an SDK, jmods and Maven artifacts. JavaFX 27 needs JDK 25; JavaFX 25 is the current long-term-support line.
- A program on the class path needs
--module-pathto the SDK’slib,--add-modules javafx.controls, and--enable-native-access=javafx.graphicsto run without a warning on JDK 25. Application.launchconstructs the application and callsstarton the JavaFX Application Thread,initon the launcher thread, andstoponce the application ends.- A
Stageis a window, aSceneits content, and the scene graph the tree of nodes under the scene’s root. - In a compact source file,
import module javafx.controls;needs a single-type import forLabel, and theApplicationmust be a nested static class. - With Maven, the dependency
javafx-controlsandmvn javafx:runreplace the hand-written command line.
References
- OpenJFX: JavaFX, the open-source client application platform (OpenJFX)
- Getting Started with JavaFX (OpenJFX)
- JavaFX downloads and release roadmap (Gluon)
- GNU General Public License, version 2, with the Classpath Exception (OpenJDK)
- Release Notes for JavaFX 27 (OpenJDK)
- Release Notes for JavaFX 26 (OpenJDK)
- Class Application (JavaFX 27) (OpenJFX)
- Class Platform (JavaFX 27) (OpenJFX)
- JEP 511: Module Import Declarations (OpenJDK)
- JEP 512: Compact Source Files and Instance Main Methods (OpenJDK)
- JEP 472: Prepare to Restrict the Use of JNI (OpenJDK)
- JEP 458: Launch Multi-File Source-Code Programs (OpenJDK)
- The java Command (Java SE 25): JDK_JAVA_OPTIONS (Oracle)
- Maven plugin to run JavaFX 11+ applications (javafx-maven-plugin) (OpenJFX)
- org.openjfx:javafx-maven-plugin versions on Maven Central (Maven Central)
- Gradle plugin org.openjfx.javafxplugin (Gradle Plugin Portal)
- Syllabus of Object Oriented Programming-I (subject code 3140705) (Gujarat Technological University)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress