JavaFX Fundamentals & the Scene Graph

The Application Class & Lifecycle

18 min Lesson 2 of 12

The Application Class & Lifecycle

Every JavaFX program revolves around a single abstract class: javafx.application.Application. Subclassing it and implementing its abstract method start() is the minimum contract required to put a window on screen. But the Application class does far more than just call start() — it manages a carefully sequenced lifecycle that initialises the toolkit, hands off to your code on the correct thread, and tears everything down cleanly when the user closes the last window. Understanding that lifecycle is what separates a JavaFX developer from someone who merely copied a "Hello World" example.

The Three Lifecycle Methods

Application defines three overridable methods that are called in a guaranteed order:

  1. init() — called once on the JavaFX Launcher thread before the GUI is created. Override this to load configuration, open database connections, or do any heavyweight work that does not touch UI nodes. The default implementation does nothing.
  2. start(Stage primaryStage) — called on the JavaFX Application Thread (the UI thread). This is the only abstract method; you must implement it. By the time it is called the toolkit is fully initialised and it is safe to create and show Stage and Scene objects. The primaryStage parameter is the main window provided by the platform.
  3. stop() — called once on the JavaFX Application Thread after the last window closes (or after Platform.exit() is invoked). Override this to release resources — close file handles, stop background threads, flush caches. The default implementation does nothing.
Thread ownership matters. init() runs on the launcher thread — you cannot create Stage or any UI node there. start() and stop() run on the JavaFX Application Thread — all GUI operations belong here. Violating these rules causes IllegalStateException at runtime.

The launch() Method

Your main() method's only real job is to call Application.launch(). This static method:

  1. Starts the JavaFX toolkit and its event-dispatch thread.
  2. Instantiates your Application subclass (using its no-argument constructor).
  3. Calls init() on the launcher thread.
  4. Calls start(primaryStage) on the JavaFX Application Thread.
  5. Blocks until the application exits.
  6. Calls stop() on the JavaFX Application Thread.

The most common form passes the class literal and any command-line arguments:

public static void main(String[] args) { Application.launch(MyApp.class, args); }

When called from within the Application subclass itself, the overload that omits the class is equivalent:

public static void main(String[] args) { launch(args); // infers the calling class at runtime }
Modern JDKs with modular JavaFX (Java 11+): if you are running with the JavaFX SDK as external modules (common with OpenJFX), pass the module path and --add-modules javafx.controls,javafx.fxml to the JVM. The Application.launch() call itself is unchanged — the change is in how you start the JVM, not in the code.

A Minimal Application — Annotated

The listing below is the smallest complete JavaFX program. Read each comment carefully; every line has a specific reason to exist.

import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.stage.Stage; public class HelloApp extends Application { // Called on the launcher thread — safe for non-GUI init work. // Omitted here because we have nothing to initialise up front. @Override public void start(Stage primaryStage) { // primaryStage is supplied by the platform — never instantiate Stage here. Label label = new Label("Hello, JavaFX!"); Scene scene = new Scene(label, 400, 200); // root node, width, height primaryStage.setTitle("My First App"); primaryStage.setScene(scene); primaryStage.show(); // makes the window visible } // stop() not overridden — nothing to clean up public static void main(String[] args) { launch(args); // hands control to the JavaFX runtime } }

Passing Parameters to Your Application

Any strings passed after the class name on the command line (or through launch(args)) are accessible inside init() and start() via getParameters(). This is JavaFX's built-in mechanism for reading launch-time configuration without reaching for System.getenv() or a static field.

@Override public void start(Stage stage) { Parameters params = getParameters(); // Named parameters: --mode=dark --user=Alice String mode = params.getNamed().getOrDefault("mode", "light"); String user = params.getNamed().getOrDefault("user", "Guest"); // Unnamed (positional) parameters List<String> raw = params.getRaw(); stage.setTitle("Hello " + user + " [" + mode + " mode]"); // ... rest of start() }

Clean Shutdown

JavaFX exits automatically when the last window is closed, but you have fine-grained control:

  • Platform.setImplicitExit(false) — prevents automatic exit when the last window closes. Useful for system-tray apps that hide their window without quitting.
  • Platform.exit() — requests an orderly shutdown. It triggers stop() and then terminates the JavaFX Application Thread. Call this from any thread.
  • Calling System.exit() skips stop() entirely — avoid it unless you are in an unrecoverable error state.
Never call System.exit() as a normal quit path. It bypasses stop(), so any cleanup code you placed there — flushing writes, releasing locks, saving state — will silently be skipped. Always use Platform.exit() to close a JavaFX application gracefully.

Lifecycle in Practice — a Realistic Template

Real applications override all three lifecycle hooks. Here is a template you can copy and adapt:

import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class RealApp extends Application { private ExecutorService workerPool; @Override public void init() { // Runs on launcher thread — safe for blocking I/O, DB connections, etc. workerPool = Executors.newFixedThreadPool(4); System.out.println("init() — thread: " + Thread.currentThread().getName()); } @Override public void start(Stage primaryStage) { System.out.println("start() — thread: " + Thread.currentThread().getName()); StackPane root = new StackPane(); Scene scene = new Scene(root, 800, 600); primaryStage.setTitle("Real App"); primaryStage.setScene(scene); primaryStage.setOnCloseRequest(e -> Platform.exit()); primaryStage.show(); } @Override public void stop() { // Runs on JavaFX Application Thread — clean up background resources. System.out.println("stop() — thread: " + Thread.currentThread().getName()); workerPool.shutdownNow(); } public static void main(String[] args) { launch(args); } }

Run this and watch the console — you will see exactly which thread each method executes on, confirming the lifecycle sequence you learned above.

Summary

The Application class is the entry point and lifetime owner of every JavaFX program. init() handles pre-GUI setup on the launcher thread; start() builds and shows the UI on the Application Thread; stop() releases resources when the application closes. Application.launch() orchestrates all of this from your main() method. Keeping heavyweight work in init(), UI work in start(), and cleanup in stop() gives you a clean, predictable application structure from day one. In the next lesson you will explore the Stage and Scene objects that start() receives and creates.