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

  1. Why This Page Exists
  2. FXML File Structure
    1. Example: student-view.fxml
    2. Key FXML Concepts
  3. The @FXML Annotation
    1. Controller for student-view.fxml
    2. How @FXML Works
  4. Loading FXML with FXMLLoader
    1. Application Class
    2. What Happens When FXMLLoader Runs
    3. The initialize() Method
  5. File Organisation
  6. Scene Builder (Optional)
  7. FXML vs Java-Only: When to Use Which
  8. Quick Check
  9. Code Completion
  10. Spot the Error
  11. Predict the Output
  12. Practice Exercises
    1. Core
    2. Extension
    3. Challenge
  13. 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

  1. Reads the FXML file and creates all the UI nodes
  2. Creates an instance of the Controller (specified by fx:controller)
  3. Injects each fx:id element into the matching @FXML field
  4. Wires each onAction to the matching @FXML method
  5. Calls initialize() on the Controller (if it exists)
  6. 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:

  1. Download Scene Builder from Gluon
  2. Open your .fxml file in Scene Builder
  3. Drag controls from the library onto the layout
  4. Set fx:id and onAction in the properties panel
  5. 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.

1public class FormController { 2 @FXML private Label status; 3 @FXML private TextField nameField; 4 5 @FXML 6 private void handleSubmit() { 7 String name = nameField.getText(); 8 status.setText("Hello, " + name); 9 } 10}

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

  1. 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 VBox as the root layout. Include appropriate fx:id and onAction attributes.

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

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

  2. Multiple FXML screens – Create two FXML files: a login screen (login-view.fxml with username/password fields and a login button) and a welcome screen (welcome-view.fxml with 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

  1. 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), and StudentApp.java (Application). All UI layout should be in the FXML file, all logic in the Controller.

Connections


© EduCS.me — A resource hub for IB Computer Science

This site uses Just the Docs, a documentation theme for Jekyll.