Lessons learned using JavaFX and FXML

Florian Enner
5 min readMay 28, 2022

I am personally a big proponent of JavaFX’s combination of FXML + CSS + Java for GUI development. When used appropriately it results in a clean separation of concerns that allows designers and developers to collaborate together without stepping on each other’s toes.

However, there are some use cases that have non-obvious solutions, and self-discovered hacks often result in code that is horrible to read and maintain. This blog post covers some of my own best practices and lessons learned while working with JavaFX and FXML (e.g. Scope).

fx:include

Complex GUIs are combinations of multiple smaller components and/or views that handle separate parts of the functionality. Trying to do everything in a single FXML file with a single controller will result in a very hard to maintain mess.

Independent components can be nested using the fx:include element. It imports the specified FXML file and instantiates the corresponding controller. The below snippet shows two components (view1.fxml and view2.fxml) that are included by the top-levelmain.fxml.

FXML files (combined into one gist because of Medium’s rendering)

Unfortunately, SceneBuilder does not list fx:include in the component library, so it needs to be added manually. However, once its added, SceneBuilder can load and edit nested views just fine. Right-clicking on an fx:include element provides an option to open the target file.

main.fxml in SceneBuilder

Referencing fx:include Controllers

Every FXML element with an fx:idattribute gets assigned to a matching @FXML annotated field or method in the backing Java controller. A really simple controller for the nested views would be

The fx:include element is a special case that assigns not one, but two variables:

  1. the root pane gets assigned to a variable with the specified fx:id name
  2. the backing controller gets assigned to a variable named fx:id+"Controller"

While I have rarely needed to reference the nested controller, I’m sure there are use cases where it is useful.

For example, this post was originally meant as a response to How to Swap Scenes Properly which explains how to switch the main view of an application (e.g. for a wizard-like application with multiple steps). We could achieve the same result by treating view1.fxml and view2.fxml as standalone steps and setting the actions in the initialize method of the root-level controller as it holds the references and knows about the full application flow

Dependency Injection

GUIs usually have a variety of services and shared state that needs to be accessed by various controllers and UI elements. This is not handled well by JavaFX out of the box, and I’d strongly recommend taking a look at external frameworks like Adam Bien’s Afterburner.fx.

Afterburner.fx is a small dependency with only a few hundred lines of code that adds Dependency Injection via JSR-330javax.inject annotations to FXML controllers. It also takes care of loading appropriate css and property files by file naming convention, and adds simple ways to asynchronously load FXML files to improve load times.

For example, let’s assume that the application has a configuration screen that lets users set two properties: (1) the desired display units for the entire application, and (2) a debug toggle that can show or hide specific features of the UI.

Independent of where the values actually get set, a simple UiConfig class with two properties could like this:

The configuration class can be injected into any controller by adding an @Inject anotation:

(The lombok @Getter annotation was added for the binding step)

FXML Bindings

FXML can do simple bindings using the ${propertyName}syntax. I generally prefer to have most binding logic in the controller, but for simple bindings it can be nice to remove the field reference and avoid complicating the Java code.

The example below uses bindings for two common cases

  • It binds text to always show the current display units
  • It hides a section that should only be shown when debug mode is enabled. (Setting managed also removes the node from the layout, so it looks the same as if it were removed entirely. Setting only visible would result in unused empty space)

FXML bindings require all of the accessor methods, so I would recommend to keep them in a separate class so that the generated code does not impact the readability of the controller logic.

SceneBuilder cannot evaluate the bindings and shows a warning sign, but besides that it doesn’t complain.

The MAPS Inspector video shows the hiding concept in action. The SceneBuilder Preview with all components visible looks like the screenshot below, and at runtime various parts and overlays get removed based on the application state.

Persistence

There is often some state that needs to be persisted between application runs, e.g., it would be frustrating for users if they had to set their preferred units after every start.

The easiest way I found for doing this is to usejava.util.prefs.Preferences . From the docs:

This class allows applications to store and retrieve user and system preference and configuration data. This data is stored persistently in an implementation-dependent backing store. Typical implementations include flat files, OS-specific registries, directory servers and SQL databases. The user of this class needn’t be concerned with details of the backing store.

To better integrate this into JavaFX I wrote a PersistentProperties utility class that loads properties in the beginning and stores them when the application stops (more specifically, when @PreDestroyis called by Afterburner’s Injector::forgetAll).

By changing a few lines the properties are now saved between runs

Closing Thoughts

I’m sure I forgot plenty of things, so I may add more later on. I hope this was useful. Good luck!

--

--