The FXMLLoader object is a mixed-function class with the responsibility to parse FXML content (as XML), build the Scene Graph and initialise the Controller of a JavaFX View.

JavaFX’s FXML markup language allows you to separate the business logic of a program from the user interface design. This enables you to design a fully-formed UI using markup language (FXML) and control it using Java code.

If you’ve got to this article looking to fix a specific issue, jump down to the troubleshooting section now.

What we’ll cover in this article

The FXMLLoader object can almost be as complicated as you want to make it. In its simplest form, it only needs the location of the FXML file. But, with a little practice, you can master complete customisation of the loading process.

In this tutorial we’ll:

  • Set up the FXMLLoader with the location of your FXML file.
  • Use instance loading to inject data into your Controller after Scene Graph building.
  • Manually set your Controller to inject data before Scene Graph building.

We’ll also debug some common problems with the FXMLLoader:

  • NullPointerException in initialize() method
  • NullPointerException in Controller constructor
  • Values being overwritten after we’ve set them
  • “Location is not set” errors when loading FXML files

We won’t have time to cover proper dependency injection in the FXML loading process, but we have covered it in this article here:

<<Link to Dependency Injection Article>>

Using The FXMLLoader

The FXMLLoader class can be used in either its static, or instanced forms. In either case, the only information we need to provide to the FXMLLoader is the location of the FXML file.

//FXMLLoader instance
FXMLLoader loader = new FXMLLoader(location);
Parent root = loader.load();
//Statically
Parent root = FXMLLoader.load(location);

In the simplest case, the FXMLLoader identifies and creates a Controller object by looking for the fx:controller attribute in the FXML file. The fx:controller attribute can only be applied to the root element of a View (that’s the first node you define in your FXML file).

1. Instance loading to inject data into your Controller

One of the main advantage of creating an instance of your FXMLLoader object is the code you can write in after executing the load() command.

If you’ve grabbed your root element straight from a static FXMLLoader.load() then the FXMLLoader object has already forgotten your Controller object. However, if you create an instance of the FXMLLoader – loader – you can grab the Controller object with a simple call the object’s getter.

The class returned by the loader.getController() method will be the same as the class referred to in the fx:controller attribute we mentioned above. In our example, we’re starting from the code we created in the Simple Maven Application tutorial. So, in this case, the controller object is actually just a class called Controller.

FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();

To implement this, we’ll have to add some control to our otherwise-empty FXML file. Let’s add a label:

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:controller="com.edencoding.Controller"
          xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
    <Label fx:id="label" />
</GridPane>

We’ve added the attribute fx:id=”label” to our Label, which instructs the FXMLLoader to connect it to an object in our Controller class.

It’s worth saying we don’t need to create an object in the Controller. No errors are going to be thrown if we don’t, but we also won’t be able to use it either…

The FXMLLoader object is smart enough that you can create a public Label and it will connect it based only on the name using reflection. If you don’t want it to be public, you can also create a private Label and annotate it as @FXML.

package com.edencoding;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Controller {
    @FXML
    private Label label;
    public void setLabelText(String text) {
        label.setText(text);
    }
}

We’ve also added the setter method, which gives us access to the Label’s setText() method.

Now, in our Main class, we expand our FXMLLoader code to set the value after we’ve loaded the FXML. This is just the start() method:

@Override
public void start(Stage primaryStage) throws Exception{
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample.fxml"));
    Parent root = loader.load();
    Controller controller = loader.getController();
    controller.setLabelText("Injected Text");
        
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(root, 300, 275));
    primaryStage.show();
}

If you’re not interested in doing anything else with the Controller object after this point, you can make your code more concise. By fixing the type as you fetch the Controller, you immediately get access to its methods in one line:

loader.<Controller>getController().setLabelText("Injected Text");

Either way, the injected text is set on the Label to produce a simple application with some custom text.

We can access the controller methods through the FXMLLoader after the load() process has run to inject information such as text strings.

2. Manually Setting the Controller

By default, the FXMLLoader creates the Controller as part of the XML-parsing phase of the load process. However, if you remove the fx:controller attribute from the FXML file, you can manually create the controller yourself and pass it to the

It’s worth noting the mode graceful way is to do it with a little dependency injection, but as a quick work-around it’s OK.

To make this work, we’ll remove the fx:controller attribute from the FXML file, and create a controller object that we pass to the Loader.

Inside the controller, we’ll create a parameterised constructor, which allows us to set the text, and then we’ll use the initialize() method to transfer that text to the label.

A lot of tutorials will teach you to use the Initializable interface inside your controllers. This takes a URL and a ResourceBundle, which you probably won’t use. To improve the conciseness of your code, you can now set up a no-arg initialize() method, which the FXMLLoader will automatically find and execute for you. This is the preferred method if you’re not going to use the parameters anyway.

package com.edencoding;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Controller {
    @FXML
    private Label label;
    private String labelText;
    public Controller(String text) {
        labelText = text;
    }
    public void initialize() {
        label.setText(labelText);
    }
}

Note: Because the scene graph hasn’t been created yet, if we try to set the text in the constructor, we’ll get a NullPointerException.

Then, in our Main class, we’ll pre-create our Controller and pass it to the loader object:

Controller controller = new Controller("Pre-Injected Text");
FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample.fxml"));
loader.setController(controller);
Parent root = loader.load();

Again, the output isn’t impressive, but then again we didn’t add much functionality either!

The order of method calls in both of these examples was vitally important. In fact, 75% of the problems you can encounter with the FXMLLoader come from the order in which things are constructed and populated by the loader.

In the next section, we’ll take a really detailed look into exactly the FXMLLoader works, and use that information to solve some common problems with the FXMLLoader.

Common issues with the FXMLLoader

There are a number of common pitfalls in FXML loading, which generally boil down to two things:

  1. The location of resources and the FXMLLoader’s ability to find them.
  2. The order of method calls in the FXMLLoader load() method.

We’ll deal with both in turn – and how to fix them.

1. “Location is not set” errors in FXMLLoader

The only time the FXMLLoader class will generate the error “Location is not set” is if the URL we provided during the constructor call didn’t point to a valid file.

It’s easy to miss this, because the parameter we pass to the FXMLLoader during construction is labelled ‘location’, so we usually see this error and think “but I did set it!”

But what we actually pass to the FXMLLoader is a URL – and we generally create the URL in-situ using the convenience method getClass().getResource(String location). If you look at how the getResource() method operates internally, we can see it uses simple logic to try and find the resouce:

  1. Create an absolute resource name using the following rules:
    • If location given begins with “/”, use the rest of the location as the absolute path
    • If it doesn’t, use the package name and then append the location.

Top debugging tip:
Rather than passing our URL to the FXMLLoader blind using getClass().getResource(), create a URL first:
URL location = getClass().getResource(“sample.fxml”);

Now you can test whether this is null before passing it to the FXMLLoader either by printing it to the console, or using your IDE’s in-build debugging tools.

In our case, let’s see the different ways we could load the sample.fxml file:

  1. We use the location “/sample.fxml”: the getResources() method will look directly in the resources folder.
  2. Instead, we use “sample.fxml”: it will look for resources/com/edencoding/sample.fxml

In our case, our project structure looks like this, so we need to use “/sample.fxml”.

If you have a more complicated project with multiple packages and you want to split your resources by package, this is the time to start using more complicated resources names.

2. NullPointerExceptions and overwritten values

The second source of stress from FXMLLoader objects is values that are overwritten once set. Or worse – trying to operate on an object just to find it it’s there.

In each of these cases, the problem is almost always down to the order you’re trying to do things. To fix that, let’s go through the order the FXMLLoader does things, and then look at how to solve each of three issues:

  • Nodes created in the constructor have been overwritten
  • NullPointerExceptions in my controller’s constructor
  • NullPointerExceptions in my initialize() method

How the FXMLLoader Object Works

Overall, the FXML loading process occurs in two phases:

  1. Parse the FXML file to create the scene graph (nodes) and Controller
  2. Populate the controller with any resources provided.

In fact, during this process, the FXMLLoader uses each of the resources we set in a specific order.

Most of the time, we won’t set a BuilderFactory, ControllerFactory or ResourceBundle, but the FXMLLoader will still do these processes in the same order.

FXMLLoader injects information into the controller and the view when provided with the correct methods and resources

Let’s look at why that’s important:

NullPointerExceptions in my constructor

The controller class object is initialised during the XML-parsing phase whenever the XML-parser finds the fx:controller attribute. Because it’s on the root node, it should be close to the beginning of this process, but we can’t decide the exact implementation and order of XML parsing.

That means trying to operate on nodes in the constructor will result in a NullPointerException because they haven’t been created yet.

Fix:
Instead of trying to operate on the scene’s nodes in the constructor, create an initialize() method. You can either do this by implementing the Initializable interface (old) or create a no-argument initalize() method. You don’t even need to annotate it. If it’s there, the FXMLLoader will find it and execute it.
Because the initialize() method is called after scene-graph creation, all your nodes will be there, safe and sound.

Nodes created in the constructor have been overwritten

Bear in mind the Controller object is created at some unspecified point during scene-graph creation, but probably towards the beginning. That means that if you try to create nodes in the constructor, all your good work will be completely replaced by the FXMLLoader.

For each node that the FXMLLoader finds, it creates the node using the BuilderFactory (the default BuilderFactory is the JavaFXBuilderFactory) and injects it into your controller.

That means any values of width, height, layout positions or text will be overwritten when the node is injected by the FXMLLoader further into the View creation process.

Fix:
For specifying values for nodes, do this in the initialize() method instead, which is called after scene-graph creation.

NullPointerExceptions in initialize() method

While the scene graph (the node tree that defines the display) is built ahead of controller initialisation, the View is not actually part of a scene yet.

The most common cause of this error is trying to add Scene or Stage-level functionality to controls in the View:

closeButton.setOnAction(event -> {
    closeButton.getScene().getWindow().hide();
});

Fix:
This entire process happens before the Scene or Stage are created. This is the wrong time to define listeners or functionality on these objects.

If you need to define listeners on a Scene or a Stage, consider establishing them using the caller class. Window-level logic is usually part of the window lifecycle management. This is almost never the responsibility of the Controller.

Try exposing the button using a getter, and setting the functionlity in the calling class, which has direct access to the Stage it’s using.

Some code for that might be:
@Override
public void start(Stage primaryStage) throws Exception{
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/sample.fxml"));
    Parent root = loader.load();
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(root, 300, 275));
        controller.getCloseButton().setOnAction(event -> {
            primaryStage.hide();
        });
    primaryStage.show();
}

Conclusions

The FXMLLoader object is as complicated as you want to make it. You can progress from static-loading to injecting data into your controllers with only a few steps.

In this tutorial we’ve used instances of the FXMLLoader to set data through methods, and to create completely custom controllers.

We’ve also tackled some teething problems you can have when trying to set up FXMLLoaders. In these cases, the issues generally boil down to the locations of your resources, or to the order you’re trying to accomplish things.

The next step in mastering the FXMLLoader is starting to use the Controller Factory and ResourceBundle parameters to take advantage of its in-built dependency injection capabilities.