JavaFX-Projekt anlegen: Lebenszyklus, Stage, Scene und Node
https://bildung.social/@oerinformatik/
https://oer-informatik.de/javafx-projekt-anlegen
JavaFX, OpenJFX, Gradle, Maven, Application-Lebenszyklus, Stage, Scene, Scene Graph, Node, Java-Modulsystem, Smoke-Test, Kanban
tl/dr; (ca. 24 min Lesezeit): Das erste Ticket der Desktop-App: Du legst ein JavaFX-Projekt mit Gradle (oder Maven) an, bindest OpenJFX als Bibliothek ein und schreibst eine Application-Klasse, die ein Fenster öffnet. Dabei lernst Du den Lebenszyklus mit init(), start() und stop() kennen, unterscheidest Stage, Scene und Node, löst die typischen Startfehler rund um das Modulsystem und sicherst alles mit einem Smoke-Test ab, der auch ohne Bildschirm läuft [AI-Assisted]. (Zuletzt geändert am 11.11.2026)
Dieser Artikel ist Teil einer Artikelreihe über eine Desktop-App mit JavaFX: Übersicht / Kanban-Projektstart / Projekt anlegen / FXML, Layouts und CSS / Properties, Bindings und MVVM / Backend anbinden / TableView für Buchungen / Formulare und Dialoge / Anmeldung mit Keycloak / Verwaltung und Diagramme / GUI-Tests mit TestFX / Abnahmetest und Kanban-Metriken / Paketieren mit jpackage
Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.
Dieser Artikel baut auf der Kann-Liste des Artikels Kanban-Projektstart für die Desktop-App: Board, WIP-Limits, Pull und Serviceklassen auf.
Als JavaFX-Grundlagen kann ich nach Durchlesen des Artikels…
… erklären, warum JavaFX seit Java 11 als Bibliothek (OpenJFX) eingebunden wird, und ein Projekt mit dem Gradle-Plugin oder dem Maven-Plugin von OpenJFX anlegen.
… die Methoden des Lebenszyklus einer
Application(Konstruktor,init(),start(),stop()) in die richtige Reihenfolge bringen und sagen, auf welchem Thread sie laufen.… die Begriffe Stage, Scene, Scene Graph und Node unterscheiden und den Scene Graph eines kleinen Programms als Baum skizzieren.
… ein Fenster mit Titel, Größe und einem Wurzelknoten mit Kindknoten im Code aufbauen.
… eine
module-info.javafür eine JavaFX-Anwendung schreiben und erklären, wozurequiresundexportsnötig sind.… einen Smoke-Test schreiben, der das Hauptfenster startet und prüft, ob es angezeigt wird.
Darüber hinaus kann ich als JavaFX-Expertenwissen…
… Startfehler wie „JavaFX runtime components are missing“, „package … is not visible“ oder eine
IllegalAccessExceptionbeim Start einer Ursache zuordnen und beheben.… eine JavaFX-Anwendung auch ohne Modulsystem starten und die Folgen abwägen.
Im Kanban-Projektstart hast Du das erste Ticket gezogen: JFX-01 „Projekt anlegen, leeres Fenster startet“. Jetzt wird es konkret. Am Ende dieses Artikels steht im Repository ein Projekt, das sich mit Gradle und mit Maven bauen lässt, ein Fenster mit dem Titel „Mitfahrlabor Desktop“ öffnet und einen ersten automatischen Test hat. Das Fenster ist noch schlicht, es zeigt nur eine Überschrift und einen Platzhalter für die Fahrtangebote von Mitfahrlabor. Aber es enthält schon alles, was jede JavaFX-Anwendung ausmacht: eine Application-Klasse mit Lebenszyklus, ein Fenster (Stage), einen Inhalt (Scene) und einen Baum aus Oberflächenelementen (Nodes).
Stand auf dem Board
Das Ticket JFX-01 der Desktop-App für Mitfahrlabor steht in „In Arbeit – läuft“, Du hast Deinen Namen eingetragen. Die Akzeptanzkriterien geben vor, wann es fertig ist:
| vor diesem Artikel | nach diesem Artikel | |
|---|---|---|
| JFX-01 Projekt anlegen | In Arbeit – läuft | Fertig |
| Akzeptanzkriterium 1 | gradlew run öffnet Fenster „Mitfahrlabor Desktop“ |
erfüllt |
| Akzeptanzkriterium 2 | gradlew test mit Smoke-Test, der das Fenster startet |
erfüllt |
| Bereit (WIP 3) | JFX-02, JFX-03, JFX-04 | unverändert, JFX-02 wird als Nächstes gezogen |
| Code-Stand | – | src/02-projekt-anlegen/mitfahrlabor-desktop |
Die Durchlaufzeit von JFX-01 beginnt mit dem Ziehen; das Board vermerkt deshalb „Gezogen am“ und am Ende „Fertig am“. Die fortgeschriebene Workflow-Definition liegt als src/02-projekt-anlegen/board.md im Repository.
OpenJFX: JavaFX ist eine Bibliothek
Bis Java 10 gehörte JavaFX zum JDK von Oracle. Seit Java 11 ist es ausgelagert und wird als eigenständiges Open-Source-Projekt OpenJFX weiterentwickelt. Für Dich heißt das: Ein JDK allein reicht nicht, JavaFX kommt wie jede andere Bibliothek über das Build-Werkzeug ins Projekt. Die Bibliothek besteht aus mehreren Modulen:
| Modul | Inhalt |
|---|---|
javafx.base |
Grundlagen: Properties, Collections, Ereignisse |
javafx.graphics |
Application, Stage, Scene, Layout, Grafik, Fenstersystem |
javafx.controls |
Bedienelemente wie Label, Button, TableView |
javafx.fxml |
Oberflächen aus FXML-Dateien laden (ab Artikel 03) |
javafx.web, javafx.media, javafx.swing |
Browser-Komponente, Audio/Video, Brücke zu Swing |
Die Module hängen voneinander ab: Wer javafx.controls einbindet, bekommt javafx.graphics und javafx.base automatisch dazu. Anders als reine Java-Bibliotheken enthält javafx.graphics nativen Code für das Fenstersystem. Deshalb gibt es jedes Modul je Betriebssystem in einer eigenen Variante (Windows, Linux, macOS). Um die Auswahl musst Du Dich nicht kümmern, das übernehmen die Plugins von OpenJFX für Gradle und Maven.
Für die Versionen gilt: JavaFX und JDK haben dieselbe Zählung, und eine JavaFX-Version setzt ein ausreichend neues JDK voraus. Wir verwenden JavaFX 27.0.1 mit Java 25, wie das Backend der Reihe. Die jeweils aktuelle Version steht auf openjfx.io.
Das Projekt mit Gradle anlegen
Das Backend der Reihe wird mit Gradle gebaut, deshalb ist Gradle auch hier der Standard; Maven funktioniert genauso und steht im zweiten Tab. Lege einen Ordner mitfahrlabor-desktop an. Die Ordnerstruktur folgt der üblichen Konvention von Gradle und Maven:
mitfahrlabor-desktop/
├── build.gradle.kts (bzw. pom.xml)
├── settings.gradle.kts
├── gradlew, gradlew.bat, gradle/wrapper/ (Gradle Wrapper)
└── src/
├── main/java/
│ ├── module-info.java
│ └── de/dbh/mitfahrlabor/desktop/MitfahrlaborDesktopApp.java
└── test/java/
└── de/dbh/mitfahrlabor/desktop/MitfahrlaborDesktopAppSmokeTest.java
Den Gradle Wrapper erzeugst Du einmalig mit einem installierten Gradle (gradle wrapper) oder kopierst ihn aus dem Backend-Projekt; danach baut jede Person im Team mit derselben Gradle-Version. Die Build-Datei:
settings.gradle.kts enthält nur den Projektnamen:
rootProject.name = "mitfahrlabor-desktop"build.gradle.kts:
plugins {
application
id("org.openjfx.javafxplugin") version "0.1.0"
}
group = "de.dbh"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
repositories {
mavenCentral()
}
// OpenJFX ist eine Bibliothek: Das Plugin lädt die passenden Module
// für das Betriebssystem und legt sie auf den Modulpfad.
javafx {
version = "27.0.1"
modules = listOf("javafx.controls")
}
application {
mainModule = "de.dbh.mitfahrlabor.desktop"
mainClass = "de.dbh.mitfahrlabor.desktop.MitfahrlaborDesktopApp"
applicationDefaultJvmArgs = listOf("--enable-native-access=javafx.graphics")
}
dependencies {
testImplementation(platform("org.junit:junit-bom:6.1.3"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// Monocle: Grafikplattform ohne Bildschirm, nur für die Tests
testRuntimeOnly("org.testfx:openjfx-monocle:21.0.2")
}
tasks.test {
useJUnitPlatform()
jvmArgs("--enable-native-access=ALL-UNNAMED")
// Smoke-Test ohne Bildschirm (Pipeline, Server): Monocle statt Fenstersystem
systemProperty("glass.platform", "Monocle")
systemProperty("monocle.platform", "Headless")
systemProperty("prism.order", "sw")
}Das Plugin application liefert die Aufgabe run, das Plugin org.openjfx.javafxplugin den Block javafx { … }: Dort stehen die JavaFX-Version und die Module, die das Projekt direkt benutzt.
Zwei Zeilen fallen auf, die mit JavaFX nichts zu tun haben: --enable-native-access erlaubt dem Modul javafx.graphics ausdrücklich, nativen Code zu laden. Neuere JDKs warnen sonst beim Start, weil dieser Zugriff künftig ohne Erlaubnis gesperrt werden soll. Die Zeilen zu Monocle erklärt der Abschnitt zum Smoke-Test.
Die erste Application-Klasse
Jede JavaFX-Anwendung beginnt mit einer Klasse, die von javafx.application.Application erbt und die Methode start(Stage) überschreibt. Sie ist abstrakt in Application; alle anderen Methoden des Lebenszyklus haben eine leere Standardfassung und dürfen fehlen. Damit Du den Lebenszyklus beobachten kannst, gibt unsere Klasse bei jedem Schritt den Namen des Threads aus:
package de.dbh.mitfahrlabor.desktop;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MitfahrlaborDesktopApp extends Application {
public static final String TITEL = "Mitfahrlabor Desktop";
@Override
public void init() {
// läuft vor start(), noch ohne Fenster
System.out.println("init() auf " + Thread.currentThread().getName());
}
@Override
public void start(Stage stage) {
System.out.println("start() auf " + Thread.currentThread().getName());
Label ueberschrift = new Label("Meine Fahrtangebote");
ueberschrift.setId("ueberschrift");
Label platzhalter = new Label("Noch keine Fahrtangebote geladen.");
platzhalter.setId("platzhalter");
BorderPane wurzel = new BorderPane();
wurzel.setTop(ueberschrift);
wurzel.setCenter(platzhalter);
BorderPane.setMargin(ueberschrift, new Insets(12));
Scene scene = new Scene(wurzel, 800, 500);
stage.setTitle(TITEL);
stage.setScene(scene);
stage.show();
}
@Override
public void stop() {
// läuft, nachdem das letzte Fenster geschlossen wurde
System.out.println("stop() auf " + Thread.currentThread().getName());
}
public static void main(String[] args) {
launch(args);
}
}Die Methode main macht nichts anderes, als launch(args) aufzurufen. Alles Weitere übernimmt die JavaFX-Laufzeit: Sie erzeugt ein Objekt Deiner Klasse und ruft dessen Methoden in einer festen Reihenfolge auf. Du rufst start() also nie selbst auf, sondern JavaFX ruft Dich. Dieses Prinzip heißt Inversion of Control und begegnet Dir auch in Spring Boot.
Die setId(…)-Aufrufe vergeben eine Kennung für einen Node, ähnlich dem id-Attribut in HTML. Der Test weiter unten findet den Platzhalter darüber, und ab Artikel 03 hängt auch CSS daran.
Stage, Scene, Node: der Scene Graph
In start() stecken drei Ebenen, die JavaFX streng trennt. Die Begriffe stammen aus dem Theater: Die Bühne bleibt, die Szenen wechseln, und in jeder Szene stehen Darsteller.
Die Stage (
javafx.stage.Stage) ist das Fenster mit Rahmen, Titelleiste und Schaltflächen des Betriebssystems. Das erste Fenster, die primary stage, erzeugt JavaFX selbst und übergibt es anstart(). Weitere Fenster legst Du mitnew Stage()an.Die Scene (
javafx.scene.Scene) ist der Inhalt eines Fensters. Sie hat eine Größe und genau einen Wurzelknoten (root). Ein Fenster zeigt immer genau eine Scene; tauschst Du sie mitstage.setScene(…)aus, wechselt der ganze Inhalt auf einmal.Ein Node (
javafx.scene.Node) ist jedes Element im Inhalt: ein Text, ein Knopf, ein Bild oder ein Layout-Container. Nodes, die andere Nodes enthalten dürfen, erben vonParent. Dazu gehören die Layout-Container wieBorderPane,VBoxoderGridPane(Unterklassen vonPane) und die Bedienelemente (Controls) wieLabeloderButton, die intern selbst aus Nodes bestehen.
Alle Nodes einer Scene bilden zusammen einen Baum, den Scene Graph. Jeder Node hat höchstens einen Elternknoten; ein Node, den Du einem zweiten Elternknoten hinzufügst, verschwindet beim ersten. Für unser Fenster sieht der Baum so aus:
Die BorderPane teilt ihre Fläche in fünf Bereiche: top, bottom, left, right und center. Jeder Bereich nimmt höchstens einen Node auf; der center bekommt den Platz, der übrig bleibt. Diese Aufteilung passt zum Hauptfenster aus Ticket JFX-02, in dem links die Angebote und rechts die Details stehen sollen. Welche Layout-Container es sonst gibt und wie man sie in FXML beschreibt, zeigt der nächste Artikel.
Die Reihenfolge im Code ist bewusst von innen nach außen: erst die Nodes, dann der Wurzelknoten, dann die Scene, zuletzt die Stage. Erst stage.show() macht das Fenster sichtbar. Du kannst den Scene Graph aber auch nach dem Anzeigen noch ändern, etwa wurzel.setCenter(new Label("…")) aufrufen; JavaFX zeichnet das Fenster dann neu.
Typische Verwechslungen:
| Begriff | ist … | gibt es je Fenster | Beispiel im Code |
|---|---|---|---|
| Stage | das Fenster des Betriebssystems | genau eines (das Fenster selbst) | stage.setTitle(TITEL) |
| Scene | der Inhalt eines Fensters | genau eine zur selben Zeit | new Scene(wurzel, 800, 500) |
| Wurzelknoten | oberster Node der Scene, ein Parent |
genau einen je Scene | new BorderPane() |
| Node | jedes Element im Baum | beliebig viele | new Label("Meine Fahrtangebote") |
Der Lebenszyklus einer Application
launch() kehrt erst zurück, wenn die Anwendung beendet ist. Dazwischen läuft ein fester Ablauf, den das Sequenzdiagramm zeigt:
Konstruktor: Die Laufzeit erzeugt ein Objekt Deiner Klasse. Dafür braucht die Klasse einen öffentlichen Konstruktor ohne Parameter; ohne eigenen Konstruktor gibt es ihn automatisch.
init(): Hier gehört Vorbereitung hin, die keine Oberfläche braucht, etwa Konfiguration lesen oder Startparameter mitgetParameters()auswerten.init()läuft auf dem JavaFX-Launcher-Thread. Nodes darfst Du hier anlegen, aber keine Stage und keine Scene erzeugen.start(Stage): Hier baust Du die Oberfläche auf.start()läuft auf dem JavaFX Application Thread, dem einen Thread, der die Oberfläche zeichnet und auf Mausklicks und Tastatur reagiert.Laufen: Nach
start()wartet JavaFX auf Ereignisse. Solange ein Fenster offen ist, läuft die Anwendung weiter.stop(): Wird das letzte Fenster geschlossen oder ruft der CodePlatform.exit()auf, ruft JavaFXstop()auf, wieder auf dem Application Thread. Hier gibst Du Ressourcen frei, etwa offene Verbindungen.System.exit()überspringtstop(); nutze daher immerPlatform.exit().
Startest Du die Anwendung und schließt das Fenster, siehst Du auf der Konsole:
init() auf JavaFX-Launcher
start() auf JavaFX Application Thread
stop() auf JavaFX Application Thread
Für alle weiteren Artikel ist eine Regel wichtig: Den Scene Graph eines angezeigten Fensters ändert nur der JavaFX Application Thread. Wer aus einem anderen Thread (etwa beim Laden von Daten aus der REST-API) die Oberfläche ändert, bekommt eine Ausnahme oder seltsame Anzeigefehler. Wie man Arbeit im Hintergrund erledigt und das Ergebnis mit Platform.runLater(…) an den Application Thread übergibt, zeigt der Artikel Backend anbinden. Der Smoke-Test unten nutzt Platform.runLater bereits.
Module: module-info.java oder Start ohne Module
JavaFX ist selbst in Module des Java-Modulsystems (Java Platform Module System, JPMS, seit Java 9) gegliedert. Am einfachsten ist es, wenn auch die eigene Anwendung ein Modul ist. Dafür liegt im Ordner src/main/java die Datei module-info.java:
module de.dbh.mitfahrlabor.desktop {
requires javafx.controls;
exports de.dbh.mitfahrlabor.desktop;
}requires javafx.controls;erklärt, dass unser Modul die Klassen vonjavafx.controlsbenutzt. Weiljavafx.controlsseinerseitsjavafx.graphicsundjavafx.basetransitiv einbindet, kann unser Code auchApplication,StageundSceneverwenden.exports de.dbh.mitfahrlabor.desktop;gibt unser Paket für andere Module frei. Das ist nötig, weil die JavaFX-Laufzeit im Moduljavafx.graphicsunsere Application-Klasse per Reflexion erzeugt. Statt für alle zu exportieren, ginge auch gezieltexports de.dbh.mitfahrlabor.desktop to javafx.graphics;.
Das Gradle-Plugin erkennt die module-info.java und startet die Anwendung auf dem Modulpfad (module path). Fehlt eine der beiden Zeilen, zeigt sich das an typischen Meldungen:
| Symptom | Zeitpunkt | Ursache | Abhilfe |
|---|---|---|---|
package javafx.application is not visible … does not read it |
beim Kompilieren | requires javafx.controls; fehlt in module-info.java |
requires ergänzen |
IllegalAccessException … does not export de.dbh.mitfahrlabor.desktop to module javafx.graphics |
beim Start, vor init() |
exports für das Paket der Application-Klasse fehlt |
exports ergänzen |
JavaFX runtime components are missing, and are required to run this application (deutsch: „Zum Ausführen dieser Anwendung benötigte JavaFX-Runtime-Komponenten fehlen“) |
beim Start | Application-Klasse wird ohne Modul gestartet und JavaFX liegt nur auf dem Klassenpfad | JavaFX auf den Modulpfad legen oder Starter-Klasse (siehe unten) |
Start ohne Module: Nicht jedes Projekt will ein Modul sein, etwa weil eine benötigte Bibliothek nicht modularisiert ist. Ohne module-info.java landet die eigene Anwendung auf dem Klassenpfad (classpath). Startest Du dann eine Klasse, die von Application erbt, prüft der Java-Launcher, ob das Modul javafx.graphics vorhanden ist, und bricht sonst mit „JavaFX runtime components are missing“ ab. Zwei Auswege:
JavaFX trotzdem auf den Modulpfad legen:
java --module-path <javafx-jars> --add-modules javafx.controls -cp <klassen> …. Genau das tut das Gradle-Plugin für Dich, auch ohnemodule-info.java.Eine Starter-Klasse mit eigener
main-Methode, die nicht vonApplicationerbt. Dann prüft der Java-Launcher nichts, und JavaFX läuft vom Klassenpfad:
package de.dbh.mitfahrlabor.desktop;
import javafx.application.Application;
public class Starter {
public static void main(String[] args) {
Application.launch(MitfahrlaborDesktopApp.class, args);
}
}Die Starter-Klasse ist ein verbreiteter Kniff, etwa für ausführbare JAR-Dateien mit allen Abhängigkeiten. JavaFX meldet dabei allerdings die Warnung „Unsupported JavaFX configuration: classes were loaded from ‘unnamed module’“, denn offiziell unterstützt ist nur der Betrieb auf dem Modulpfad. Wir bleiben deshalb beim Modul. Für das Installationspaket mit jlink und jpackage (Artikel 12) ist ein Modul ohnehin der einfachere Weg.
Der Smoke-Test
Ein Smoke-Test prüft nur, ob ein System überhaupt startet. Der Name stammt aus der Elektronik: Man schaltet ein neues Gerät ein und schaut, ob Rauch aufsteigt. Raucht es nicht, lohnen sich genauere Tests. Für JFX-01 heißt das: Das Hauptfenster öffnet sich, trägt den richtigen Titel und enthält den Platzhalter. Was im Fenster passiert, prüfen spätere Tests mit TestFX (Artikel 10).
package de.dbh.mitfahrlabor.desktop;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import javafx.application.Platform;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class MitfahrlaborDesktopAppSmokeTest {
@BeforeAll
static void javaFxStarten() throws InterruptedException {
CountDownLatch bereit = new CountDownLatch(1);
try {
Platform.startup(bereit::countDown);
} catch (IllegalStateException schonGestartet) {
bereit.countDown(); // ein anderer Test hat die Plattform schon gestartet
}
assertTrue(bereit.await(10, TimeUnit.SECONDS), "JavaFX-Plattform startet nicht");
// Plattform weiterlaufen lassen, auch wenn ein Test sein Fenster schließt
Platform.setImplicitExit(false);
}
@Test
void hauptfensterStartetMitTitelUndPlatzhalter() throws Exception {
// start() muss auf dem JavaFX Application Thread laufen
FutureTask<Stage> fensterOeffnen = new FutureTask<>(() -> {
Stage stage = new Stage();
new MitfahrlaborDesktopApp().start(stage);
return stage;
});
Platform.runLater(fensterOeffnen);
Stage stage = fensterOeffnen.get(10, TimeUnit.SECONDS);
assertTrue(stage.isShowing());
assertEquals("Mitfahrlabor Desktop", stage.getTitle());
assertInstanceOf(BorderPane.class, stage.getScene().getRoot());
assertNotNull(stage.getScene().lookup("#platzhalter"));
Platform.runLater(stage::close);
}
}Der Test spielt die Rolle der JavaFX-Laufzeit selbst:
Platform.startup(…)startet die JavaFX-Plattform ohnelaunch(). Das geht nur einmal je JVM; laufen mehrere Testklassen in derselben JVM, wirft der zweite Aufruf eineIllegalStateException, die der Test abfängt.Platform.setImplicitExit(false)verhindert, dass JavaFX sich beendet, sobald der erste Test sein Fenster schließt. Sonst blieben alle folgenden Tests hängen.Die
FutureTaskführtstart()perPlatform.runLater(…)auf dem Application Thread aus und liefert die Stage an den Test-Thread zurück;get(10, TimeUnit.SECONDS)wartet höchstens zehn Sekunden.lookup("#platzhalter")sucht im Scene Graph nach dem Node mit dieser Kennung, wie ein CSS-Selektor.
Ohne Bildschirm testen: Auf Deinem Rechner öffnet der Test kurz ein echtes Fenster. In einer CI-Pipeline oder auf einem Server gibt es aber keinen Bildschirm, und JavaFX bricht beim Start ab. Dafür gibt es Monocle, eine Grafikplattform von OpenJFX, die nur im Speicher zeichnet. Die drei Systemeigenschaften in der Build-Datei schalten sie für die Tests ein (glass.platform=Monocle, monocle.platform=Headless, prism.order=sw für Software-Rendering). Monocle wird vom TestFX-Projekt als Bibliothek org.testfx:openjfx-monocle veröffentlicht; die Version 21.0.2 funktioniert mit JavaFX 27. Damit Monocle die internen Klassen von javafx.graphics erreicht, laufen die Tests auf dem Klassenpfad (bei Maven useModulePath=false, bei Gradle der Standard für Tests ohne eigene module-info.java). Deshalb erscheint im Testprotokoll die Warnung „Unsupported JavaFX configuration“; für die Tests ist das unkritisch.
Starten und testen
Ausgeführt wird im Projektordner der Desktop-App von Mitfahrlabor. Unter Windows rufst Du in der PowerShell das Skript mit .\ auf, unter Linux und macOS mit ./:
.\gradlew.bat run # Fenster öffnen (Maven: .\mvnw.cmd javafx:run)
.\gradlew.bat test # Smoke-Test ausführen (Maven: .\mvnw.cmd test)Beim ersten Aufruf lädt der Wrapper Gradle bzw. Maven und alle Bibliotheken herunter, das dauert einen Moment. Danach öffnet run das Fenster „Mitfahrlabor Desktop“, und test meldet BUILD SUCCESSFUL. Den vollständigen Code-Stand findest Du unter src/02-projekt-anlegen/mitfahrlabor-desktop. In einer Entwicklungsumgebung wie IntelliJ IDEA, Eclipse oder VS Code importierst Du das Projekt als Gradle- bzw. Maven-Projekt und startest über die Aufgabe run bzw. das Ziel javafx:run. Ein Start über „Run“ direkt an der Klasse umgeht das Plugin und endet oft mit „JavaFX runtime components are missing“.
Nächster Schritt: JFX-01 nach „Fertig“, JFX-02 ziehen
Beide Akzeptanzkriterien sind erfüllt: run öffnet das Fenster, test läuft grün, auch in der Pipeline ohne Bildschirm. Das Ticket wandert über „erledigt“ in den Test; eine zweite Person aus dem Team prüft die Kriterien und liest den Code, danach steht JFX-01 in „Fertig“. In „In Arbeit“ ist damit wieder Platz, und Du ziehst JFX-02 „Hauptfenster mit FXML und CSS“. Im nächsten Artikel, FXML, Layouts und CSS, wandert der Aufbau der Oberfläche aus dem Java-Code in eine FXML-Datei, und das Fenster bekommt Layout und Gestaltung.
Zum Üben gibt es die Übungsaufgaben zum JavaFX-Projekt: Dort ordnest Du den Lebenszyklus, baust ein Fenster für die Mitfahrgesuche und behebst Startfehler.
Leitfragen
- Warum genügt seit Java 11 ein JDK nicht mehr, um eine JavaFX-Anwendung zu bauen, und wie kommt JavaFX ins Projekt?
JavaFX ist seit Java 11 nicht mehr Teil des JDK, sondern das eigenständige Projekt OpenJFX. Es wird wie eine Bibliothek über das Build-Werkzeug eingebunden, mit dem Gradle-Plugin org.openjfx.javafxplugin oder als Abhängigkeit org.openjfx:javafx-controls mit dem javafx-maven-plugin; die Plugins wählen die Variante für das Betriebssystem. siehe Abschnitt „OpenJFX“
- In welcher Reihenfolge ruft JavaFX die Methoden einer Application auf, und auf welchem Thread laufen sie?
Konstruktor, init() (JavaFX-Launcher-Thread), start(Stage) (JavaFX Application Thread), dann läuft die Anwendung, bis das letzte Fenster geschlossen oder Platform.exit() aufgerufen wird, zuletzt stop() (JavaFX Application Thread). siehe Abschnitt „Lebenszyklus“
- Was ist der Unterschied zwischen Stage, Scene und Node?
Die Stage ist das Fenster des Betriebssystems, die Scene der Inhalt eines Fensters mit genau einem Wurzelknoten, ein Node jedes Element im Inhalt (Layout-Container, Bedienelement, Text). Alle Nodes einer Scene bilden einen Baum, den Scene Graph. siehe Abschnitt „Stage, Scene, Node“
- Wozu dienen
requiresundexportsin dermodule-info.javaeiner JavaFX-Anwendung?
requires javafx.controls; macht die JavaFX-Klassen für den eigenen Code sichtbar (sonst Compilerfehler „package … is not visible“). exports gibt das Paket der Application-Klasse frei, damit javafx.graphics sie per Reflexion erzeugen kann (sonst IllegalAccessException beim Start). siehe Abschnitt „Module“
- Was bedeutet die Meldung „JavaFX runtime components are missing“, und wie behebst Du sie?
Eine Klasse, die von Application erbt, wurde gestartet, ohne dass das Modul javafx.graphics auf dem Modulpfad liegt, etwa über „Run“ in der Entwicklungsumgebung am Plugin vorbei. Abhilfe: über das Plugin starten (gradlew run, mvnw javafx:run), JavaFX mit --module-path … --add-modules javafx.controls auf den Modulpfad legen oder eine Starter-Klasse ohne Application als Einstieg verwenden. siehe Abschnitt „Module“
- Was prüft ein Smoke-Test, und wie läuft er ohne Bildschirm?
Er prüft nur, ob die Anwendung überhaupt startet: Das Hauptfenster wird angezeigt, hat den richtigen Titel und den erwarteten Wurzelknoten. Ohne Bildschirm (Pipeline, Server) schaltet man mit den Systemeigenschaften glass.platform=Monocle, monocle.platform=Headless und prism.order=sw auf die Grafikplattform Monocle um, die nur im Speicher zeichnet. siehe Abschnitt „Der Smoke-Test“
- Warum darf nur der JavaFX Application Thread den Scene Graph eines angezeigten Fensters ändern, und wie kommt Code aus einem anderen Thread dorthin?
JavaFX ist nicht threadsicher: Der Application Thread zeichnet die Oberfläche und verarbeitet Ereignisse; Änderungen aus anderen Threads führen zu Ausnahmen oder Anzeigefehlern. Code aus anderen Threads übergibt die Änderung mit Platform.runLater(…), das sie später auf dem Application Thread ausführt. siehe Abschnitt „Lebenszyklus“
Links und weitere Informationen
als Nachschlagewerk: OpenJFX: Getting Started with JavaFX
als Nachschlagewerk: JavaFX-API: Klasse Application mit Lebenszyklus
zum Gradle-Plugin: javafx-gradle-plugin auf GitHub
zum Maven-Plugin: javafx-maven-plugin auf GitHub
zum Vergleich: Spring-Boot-Projekt der Backend-Reihe anlegen
für den Einstieg: Szenario Mitfahrlabor mit Personas und Regeln
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “JavaFX-Projekt anlegen: Lebenszyklus, Stage, Scene und Node” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/javafx-projekt-anlegen veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/javafx/javafx-einstieg. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
