FXML and Controllers
IA Preparation: FXML separates your GUI layout from your Java logic, making your IA code cleaner and easier to maintain. This is the approach most teachers and examiners recommend.
Table of Contents
- Why This Page Exists
- FXML File Structure
- The @FXML Annotation
- Loading FXML with FXMLLoader
- File Organisation
- Scene Builder (Optional)
- FXML vs Java-Only: When to Use Which
- Quick Check
- Code Completion
- Spot the Error
- Predict the Output
- Practice Exercises
- Connections
Why This Page Exists
In JavaFX Basics and MVC Architecture, we built the UI entirely in Java code – creating buttons, labels, and layouts line by line. This works, but it mixes what the UI looks like with what the program does.
FXML is an XML-based markup language that lets you define your UI layout in a separate file. A Controller class then handles the logic. This gives you a cleaner separation:
| Without FXML | With FXML |
|---|---|
| UI layout written in Java | UI layout written in XML |
| Hard to visualise the interface from code | XML structure mirrors the visual layout |
| Layout and logic mixed in one file | Layout and logic in separate files |
| Changes to the UI require recompiling | Layout changes only need editing the .fxml file |
FXML File Structure
An FXML file describes your window’s layout using XML tags that correspond to JavaFX classes.
Example: student-view.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml"
fx:controller="StudentController">
<padding><Insets top="10" right="10" bottom="10" left="10"/></padding>
<center>
<TextArea fx:id="display" editable="false" prefRowCount="12"/>
</center>
<bottom>
<VBox spacing="10">
<padding><Insets top="10"/></padding>
<TextField fx:id="nameField" promptText="Student name"/>
<TextField fx:id="gradeField" promptText="Grade (0-100)"/>
<HBox spacing="10">
<Button text="Add" onAction="#handleAdd"/>
<Button text="Remove" onAction="#handleRemove"/>
<Button text="Search" onAction="#handleSearch"/>
</HBox>
<Label fx:id="status" text="Ready"/>
</VBox>
</bottom>
</BorderPane>
Key FXML Concepts
fx:controller – Links this FXML file to a Java Controller class. The Controller handles all the logic.
fx:id – Gives a control a unique identifier so the Controller can access it. This is like naming a variable.
onAction="#methodName" – Links a button click (or Enter key on a TextField) to a method in the Controller. The # prefix means “call this method in the controller.”
<?import ...?> – Imports JavaFX classes, just like import statements in Java.
Every fx:id in the FXML must have a matching @FXML field in the Controller, and every onAction="#method" must have a matching @FXML method.
The @FXML Annotation
The @FXML annotation tells JavaFX to inject the UI element from the FXML file into the Controller’s field. Without it, the field would be null.
Controller for student-view.fxml
import javafx.fxml.FXML;
import javafx.scene.control.*;
public class StudentController {
private StudentList model;
@FXML private TextField nameField;
@FXML private TextField gradeField;
@FXML private TextArea display;
@FXML private Label status;
public void setModel(StudentList model) {
this.model = model;
refreshDisplay();
}
@FXML
private void handleAdd() {
String name = nameField.getText().trim();
String gradeText = gradeField.getText().trim();
if (name.isEmpty()) {
status.setText("Error: Name cannot be empty.");
return;
}
int grade;
try {
grade = Integer.parseInt(gradeText);
} catch (NumberFormatException e) {
status.setText("Error: Grade must be a number.");
return;
}
try {
Student s = new Student(name, grade);
model.addStudent(s);
status.setText("Added: " + s);
nameField.clear();
gradeField.clear();
refreshDisplay();
} catch (IllegalArgumentException e) {
status.setText("Error: " + e.getMessage());
}
}
@FXML
private void handleRemove() {
String name = nameField.getText().trim();
if (name.isEmpty()) {
status.setText("Error: Enter a name to remove.");
return;
}
if (model.removeByName(name)) {
status.setText("Removed: " + name);
nameField.clear();
refreshDisplay();
} else {
status.setText("Not found: " + name);
}
}
@FXML
private void handleSearch() {
String name = nameField.getText().trim();
if (name.isEmpty()) {
status.setText("Error: Enter a name to search.");
return;
}
Student found = model.findByName(name);
if (found != null) {
status.setText("Found: " + found);
} else {
status.setText("Not found: " + name);
}
}
private void refreshDisplay() {
display.clear();
for (int i = 0; i < model.getAll().size(); i++) {
display.appendText((i + 1) + ". " + model.getAll().get(i) + "\n");
}
}
}
How @FXML Works
| FXML | Controller | What Happens |
|---|---|---|
fx:id="nameField" | @FXML private TextField nameField; | JavaFX sets the field to the TextField from the FXML |
onAction="#handleAdd" | @FXML private void handleAdd() | Button click calls this method |
The field name in Java must exactly match the fx:id in FXML. If fx:id="nameField" but the Java field is nameInput, the connection fails and the field will be null.
Loading FXML with FXMLLoader
The FXMLLoader class reads an FXML file, creates the UI elements, and connects them to the Controller.
Application Class
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class StudentApp extends Application {
@Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("student-view.fxml")
);
Parent root = loader.load();
// Get the controller and pass the model
StudentController controller = loader.getController();
controller.setModel(new StudentList());
stage.setTitle("Student Manager");
stage.setScene(new Scene(root, 450, 450));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
What Happens When FXMLLoader Runs
- Reads the FXML file and creates all the UI nodes
- Creates an instance of the Controller (specified by
fx:controller) - Injects each
fx:idelement into the matching@FXMLfield - Wires each
onActionto the matching@FXMLmethod - Calls
initialize()on the Controller (if it exists) - Returns the root node ready to be placed in a Scene
The initialize() Method
If your Controller has a method called initialize(), JavaFX calls it automatically after all @FXML fields have been injected. Use it for setup that needs the UI to be ready.
@FXML
private void initialize() {
// Called automatically after FXML is loaded
// All @FXML fields are now available
status.setText("Ready -- " + java.time.LocalDate.now());
}
Do not set up the Model in initialize(). The Controller does not have the Model yet at that point. Use a setModel() method called from the Application class instead.
File Organisation
A typical FXML-based IA project has this structure:
src/
├── StudentApp.java // Application -- loads FXML, wires Model
├── Student.java // Model -- data class
├── StudentList.java // Model -- collection management
├── StudentController.java // Controller -- event handlers
└── student-view.fxml // View -- UI layout
Convention: Place the FXML file in the same package/directory as the Controller. This makes getClass().getResource("filename.fxml") work without path issues.
For larger IA projects with multiple screens:
src/
├── App.java
├── model/
│ ├── Student.java
│ └── StudentList.java
├── controller/
│ ├── MainController.java
│ └── AddStudentController.java
└── view/
├── main-view.fxml
└── add-student-view.fxml
Scene Builder (Optional)
Scene Builder is a free visual editor for FXML files. Instead of writing XML by hand, you drag and drop controls onto a canvas.
Advantages:
- See the layout as you build it
- Set properties (text, size, alignment) in a panel
- Automatically generates valid FXML
How to use it:
- Download Scene Builder from Gluon
- Open your
.fxmlfile in Scene Builder - Drag controls from the library onto the layout
- Set
fx:idandonActionin the properties panel - Save – the FXML file is updated automatically
Scene Builder is helpful but not required. Many students write FXML by hand, especially for simple layouts. Use whatever approach feels more comfortable.
FXML vs Java-Only: When to Use Which
| Approach | Best For |
|---|---|
| FXML | Multi-screen IA projects, complex layouts, clean separation of concerns |
| Java-only | Simple single-screen apps, rapid prototyping, learning JavaFX basics |
For your IA, FXML is recommended because:
- It produces cleaner, more maintainable code
- Examiners can clearly see the UI structure separate from the logic
- Adding or rearranging controls does not require changing Java code
- It naturally enforces MVC separation
Quick Check
Q1. What does the fx:id attribute do in an FXML file?
Q2. What happens if you forget the @FXML annotation on a Controller field?
Q3. What does FXMLLoader.load() do?
Q4. How does JavaFX know which Controller class to use for an FXML file?
Q5. When is the Controller's initialize() method called?
Code Completion
Complete the missing parts of this FXML-based application.
Fill in the blanks: Complete the Controller that matches an FXML file with fx:id="inputField" and a button with onAction="#handleClick".
import javafx.fxml.FXML;
import javafx.scene.control.*;
public class MyController {
private TextField inputField;
@FXML private Label output;
@FXML
private void () {
String text = inputField.;
output.setText("You typed: " + text);
inputField.;
}
} Spot the Error
This FXML and Controller pair should work together, but there is a mismatch.
Bug Hunt: The FXML has <TextField fx:id="nameInput"/> but the Controller crashes with a NullPointerException when the button is clicked.
Pick the correct fix for line 3:
Predict the Output
Predict: What text does the status label show when the window first opens?
// In FXML: <Label fx:id="status" text="Loading..."/>
// In Controller:
@FXML private Label status;
@FXML
private void initialize() {
status.setText("Welcome!");
} Practice Exercises
Core
-
FXML from scratch – Write an FXML file for a simple calculator with two TextFields (for numbers), four Buttons (+, -, *, /), and a Label for the result. Use a
VBoxas the root layout. Include appropriatefx:idandonActionattributes. -
Matching Controller – Write the Controller class that matches your calculator FXML. Each button should read the two numbers, perform the operation, and display the result in the label. Handle the case where input is not a valid number.
Extension
-
Convert to FXML – Take the Name List Manager from JavaFX Basics and refactor it: move the UI layout into an FXML file and the event handlers into a Controller class. Keep the
ArrayList<String>as a simple model. -
Multiple FXML screens – Create two FXML files: a login screen (
login-view.fxmlwith username/password fields and a login button) and a welcome screen (welcome-view.fxmlwith a greeting label). Write the Application class that loads the login screen first, and switches to the welcome screen when the button is clicked.
Challenge
- Full FXML MVC Student Manager – Rebuild the Student Manager from MVC Architecture using FXML. You should have:
Student.java(Model),StudentList.java(Model),student-view.fxml(View),StudentController.java(Controller), andStudentApp.java(Application). All UI layout should be in the FXML file, all logic in the Controller.
Connections
- Prerequisites: JavaFX Basics – controls and event handling
- Prerequisites: MVC Architecture – understanding Model-View-Controller
- Next: Multi-Window Apps – managing multiple stages and sharing data
- Next: File I/O in Apps – persisting data to files