JavaFX Controls, Layouts & FXML

Introduction to FXML

18 min Lesson 6 of 12

Introduction to FXML

So far in this tutorial you have built every UI in pure Java: you created nodes, set properties, added children, and wired listeners all inside the same class. This works for small examples, but it quickly becomes unmanageable. A 200-line start() method that mixes layout logic with business logic is difficult to read, impossible to hand to a designer, and a nightmare to test.

FXML is JavaFX's answer to this problem. It is an XML-based markup language that lets you declare your user interface in a separate file, completely independent of the Java code that drives it. Think of it as the JavaFX equivalent of HTML for web pages: the markup describes structure, the Java class handles behaviour.

Why FXML Exists

There are three concrete benefits that motivate every professional JavaFX project to use FXML:

  1. Separation of concerns — the layout file (.fxml) is purely structural. A designer or a tooling assistant can edit it without touching a single Java class.
  2. Tooling support — Scene Builder, the drag-and-drop GUI designer, reads and writes FXML natively. You drag a Button onto the canvas and Scene Builder writes the XML for you.
  3. Readability — a deeply nested layout that takes 80 Java lines becomes 30 lines of indented XML, and the hierarchy is visually obvious.
FXML is not compiled. It is loaded at runtime by FXMLLoader, which parses the XML, instantiates the nodes, sets property values, and injects them into a controller class you provide. This means you can update the layout without recompiling your Java code — very useful during UI iteration.

Anatomy of an FXML File

An FXML file is a standard XML document. The root element is almost always a layout pane or a container node. Attributes map directly to JavaFX node properties. Here is the simplest possible example — a VBox holding a Label and a Button:

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Button?> <VBox spacing="12" xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.HelloController"> <Label text="Hello, FXML!" /> <Button text="Click Me" onAction="#handleClick" /> </VBox>

A few things to notice:

  • <?import ...?> processing instructions work exactly like Java import statements. Every class you reference in the markup must be imported this way.
  • xmlns:fx="http://javafx.com/fxml" declares the fx: namespace, which gives access to special FXML attributes like fx:id and fx:controller.
  • fx:controller tells FXMLLoader which Java class is the controller for this file. The loader will instantiate it automatically.
  • onAction="#handleClick" — the # prefix means "look up a method named handleClick on the controller".

Loading FXML with FXMLLoader

The bridge between your FXML file and your running application is javafx.fxml.FXMLLoader. You call it once in Application.start():

import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class MainApp extends Application { @Override public void start(Stage primaryStage) throws Exception { // getResource() locates the file relative to this class on the classpath FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/hello.fxml")); Parent root = loader.load(); // parses XML, builds node tree, injects controller primaryStage.setScene(new Scene(root, 400, 250)); primaryStage.setTitle("FXML Demo"); primaryStage.show(); } public static void main(String[] args) { launch(args); } }

loader.load() returns the root node of the scene graph declared in the FXML file — in this case a VBox. The return type is usually cast to Parent or the concrete type, whichever you need.

Put FXML files under src/main/resources (Maven/Gradle layout), in a package that mirrors your Java source tree. For example, if your controller is com.example.HelloController, place the FXML at src/main/resources/fxml/hello.fxml and load it with getClass().getResource("/fxml/hello.fxml"). Keeping them on the classpath — not the filesystem — means the path works identically in your IDE, in a fat JAR, and in a native image.

Setting Properties in FXML

Any JavaFX property that has a public setter can be set as an XML attribute. Primitive types are coerced automatically; colours and enums are looked up from their string representations. Complex objects are expressed as nested child elements:

<?import javafx.scene.layout.VBox?> <?import javafx.scene.control.TextField?> <?import javafx.geometry.Insets?> <VBox spacing="10" xmlns:fx="http://javafx.com/fxml"> <!-- Nested property element syntax for padding --> <VBox.margin> <Insets top="10" right="10" bottom="10" left="10" /> </VBox.margin> <TextField fx:id="nameField" promptText="Enter your name" prefWidth="250" /> </VBox>

The fx:id attribute is special: it tells FXMLLoader to inject this node into a matching @FXML-annotated field in the controller. That connection is the topic of the next lesson; for now just know the naming convention: fx:id must match the Java field name exactly.

The FXML Namespace and Special Attributes

The fx: namespace provides a small set of powerful attributes:

  • fx:id — names a node so the controller can reference it.
  • fx:controller — specifies the controller class (set once on the root element).
  • fx:include — embeds another FXML file, enabling modular layouts.
  • fx:define — declares objects outside the scene graph (useful for shared resources).
  • fx:root — enables the custom component pattern where a class IS both the root node and its own controller.

Static vs Property Element Syntax

JavaFX FXML supports two syntaxes for setting values. The attribute syntax is concise and works for strings and simple types:

<Button text="Save" prefWidth="100" />

The property element syntax is used for values that are objects themselves, such as Insets, Font, or ObservableList:

<Button> <font> <Font name="Arial Bold" size="14" /> </font> </Button>

You will see both forms used together in real FXML files. Prefer attribute syntax when it reads clearly; fall back to element syntax for complex object graphs.

Watch your getResource() path. If the path starts with / it is absolute from the classpath root. Without the leading slash it is relative to the calling class's package directory. A wrong path causes FXMLLoader.load() to throw a NullPointerException at runtime — not a compile error — so always verify the file is actually on the classpath and the path string is correct.

A Minimal Working Project Structure

A typical Maven project with FXML looks like this:

src/ main/ java/ com/example/ MainApp.java HelloController.java resources/ fxml/ hello.fxml

The Maven build copies everything under resources/ to the classpath root, so getClass().getResource("/fxml/hello.fxml") resolves correctly at runtime.

Summary

FXML separates your UI declaration from your application logic by expressing the scene graph as XML. You import classes with <?import?>, bind a controller with fx:controller, name nodes with fx:id, and wire event handlers with #methodName. FXMLLoader bridges the markup and the Java runtime: it parses the file, builds the node tree, and hands back the root node ready to be placed in a Scene. This clean separation is the foundation of every professional JavaFX application — and Scene Builder, the drag-and-drop visual editor, works entirely on top of it.