Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 20 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

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:

Hash Generator Open the SDK zip you downloaded and compare its SHA-256 with the sum Gluon lists next to the download.
JavaFX 27 on JDK 21, and the same program compiled against JavaFX 25.0.4 Python · versions_session.py
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

Welcome.java Java · Welcome.java
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:

  • Application is the class every JavaFX program extends. Its abstract method start(Stage) is where the window is built; main only calls launch(args), which starts the JavaFX runtime and then your application.
  • Stage is a window. The runtime creates the first one, the primary stage, and passes it to start.
  • Scene is 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. StackPane is a pane, a node that holds and arranges other nodes, and the Label is its only child.
A Stage holds a Scene whose root is a VBox; the VBox holds three Labels and a Quit Button, top to bottom.Stage: the windowtitle "Practical 1"Scene: the content, 320 x 200VBox: the root node(a Parent)Its children, top to bottomLabel "Welcome to Java"Label "Learning Java Now"Label "Programming is fun"Button "Quit"getChildren()

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.

  1. The outer box is the Stage, the window itself, with the title "Practical 1".
  2. Inside it is the Scene, the content of the window, 320 pixels wide and 200 high.
  3. 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:

The errors without each option, then the window Python · sdk_session.py
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/lib says 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.controls adds the module, together with javafx.graphics and javafx.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.graphics does.

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:

LifeCycle.java Java · LifeCycle.java
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");
    }
}
Which thread runs each step Python · lifecycle_session.py
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:

Source-file mode and the class-file launcher, on a class without main Python · nomain_session.py
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:

compact/WelcomeCompact.java Java · compact/WelcomeCompact.java
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);
}
The Label clash, then the fixed file Python · compact_session.py
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 interface java.lang.classfile.Label. Both imports offer a Label, 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 extends Object, so it cannot be the Application. The file declares public static class App extends Application inside it and launches that class from main.

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:

welcome-fx/pom.xml XML · welcome-fx/pom.xml
<?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>
mvn javafx:run on the life-cycle program Python · maven_session.py
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

Three ways to run a JavaFX program
Criterion The SDK and the java commandA compact source fileMaven or Gradle
What you set up Unpack the SDK onceUnpack the SDK onceA POM or build script; JavaFX is downloaded
What you type java with three optionsThe same command, on one filemvn javafx:run
Grows into a real project With effortNo: one file onlyYes, with tests, packaging and IDE support
Platform-specific files You pick the SDK for your systemYou pick the SDK for your systemChosen for you by the JavaFX POM
When to choose You want to see exactly what java needsYou try out one idea quicklyYou 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:

The starter class and its sample checks

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();
    }
}
The sample checks against the starter Python · exercise_session.py
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:

  1. Add the labels “Learning Java Now” and “Programming is fun” to the VBox, after the first one.
  2. Add a Button with the text “Quit” as the last child, and give it an action with setOnAction.
  3. 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.

  1. Question 1 of 5 java --module-path javafx-sdk-27/lib Welcome.java stops with "package javafx.application is not visible ... declared in module javafx.graphics, which is not in the module graph". What is missing from the command?

    Choose one answer.

    Show the answer to question 1

    Answer: --add-modules javafx.controls, which puts the JavaFX modules into the module graph

    The module path only says where modules are. A program on the class path reads no module until one is added to the module graph, and --add-modules javafx.controls adds it together with javafx.graphics and javafx.base, which it requires. Without any module path at all, javac says the package "does not exist" instead.

  2. Question 2 of 5 Put the steps of launching an Application in the order the JavaFX runtime runs them.

    Give each item its position, from 1 (first).

    Show the answer to question 2

    Answer:

    1. The constructor of the Application subclass
    2. init()
    3. start(Stage)
    4. stop(), after Platform.exit() or when the last window closes

    The recorded life cycle printed exactly this order. The constructor and start() ran on the JavaFX Application Thread, init() on the JavaFX-Launcher thread, and launch() returned on the main thread only after stop().

  3. Question 3 of 5 Why must init() never create a Stage or a Scene?

    Choose one answer.

    Show the answer to question 3

    Answer: It runs on the launcher thread, and windows and scenes belong on the JavaFX Application Thread

    The Application documentation says init() is called on the launcher thread and that an application must not construct a Scene or a Stage there. Load settings or data in init(); build the window in start().

  4. Question 4 of 5 On JDK 21, a program with the JavaFX 27 SDK stops before main() with "Unsupported major.minor version 69.0". What is the fix?

    Choose one answer.

    Show the answer to question 4

    Answer: Run it on JDK 25 or later, or use a JavaFX release whose minimum is JDK 21 or lower, such as 21 or 25

    Class-file version 69 is Java 25. JavaFX 27 is compiled with --release 25, so a Java 21 runtime cannot read its modules, whatever your own program was compiled for. JavaFX 25 needs JDK 23 and JavaFX 21 needs JDK 17.

  5. Question 5 of 5 This compact source file fails to compile. Why?

    Read the code, then choose one answer.

    import module javafx.controls;
    
    void main() {
        Label title = new Label("Welcome to Java");
    }
    Show the answer to question 5

    Answer: Label is ambiguous, because a compact source file also imports java.base, which has java.lang.classfile.Label

    Every compact source file imports the module java.base on demand, and both modules contain a type named Label. A single-type import, import javafx.scene.control.Label;, wins over both module imports and fixes it.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. 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?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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.

    Model answer, Pro. First step and follow-ups

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-path to the SDK’s lib, --add-modules javafx.controls, and --enable-native-access=javafx.graphics to run without a warning on JDK 25.
  • Application.launch constructs the application and calls start on the JavaFX Application Thread, init on the launcher thread, and stop once the application ends.
  • A Stage is a window, a Scene its 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 for Label, and the Application must be a nested static class.
  • With Maven, the dependency javafx-controls and mvn javafx:run replace the hand-written command line.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.