Java Module 18 – Desktop GUIs with JavaFX (and Swing for legacy code)
JavaFX properties, binding and listeners
Use JavaFX properties, bind them one way, both ways and through Bindings expressions, and pick between invalidation, change and list listeners.
What you will learn
- Use JavaFX properties and bind them one way, both ways and through Bindings expressions
- Distinguish invalidation listeners from change listeners and predict when each one fires
- Track list changes with ObservableList and ListChangeListener, and avoid listener leaks
Before you start
What you’ll learn · Before you start
What you will learn
- Use JavaFX properties and bind them one way, both ways and through Bindings expressions
- Distinguish invalidation listeners from change listeners and predict when each one fires
- Track list changes with ObservableList and ListChangeListener, and avoid listener leaks
Before you start
On this page
JavaFX properties are values that tell you when they change. A SimpleIntegerProperty holds an int as a field
would, but other code can listen to it or bind to it. Binding keeps one property equal to another, or to an
expression of several, with no code that copies values: after total.bind(theory.add(practical)), the total follows
both marks by itself. Listeners run your own code when a value changes. An invalidation listener hears that a value
may have changed, and a change listener also receives the old and the new value. Use binding for values that follow
other values, and use listeners for actions that a change should trigger, such as saving a draft or logging.
This lesson builds a marks model from JavaFX properties and bindings with nothing but the javafx.base module, so
every example runs without a window, on Eclipse Temurin 25.0.4.1 with JavaFX 27.
Properties: values that announce their changes
A JavaFX property comes with three methods named after it: theoryProperty() returns the property object, and
getTheory() and setTheory(int) read and write its value. Every node exposes its state this way. A label’s text is
textProperty(), a node’s width is widthProperty() and a shape’s colour is fillProperty(), so anything on the
screen can take part in a binding.
Each value type has its own classes. SimpleIntegerProperty, SimpleDoubleProperty, SimpleStringProperty,
SimpleBooleanProperty and SimpleObjectProperty<T> are the ordinary read-write kinds. A ReadOnlyIntegerWrapper
lets a class change a value that callers may only read. Giving a property its bean and a name,
new SimpleIntegerProperty(this, "theory", 52), costs nothing and makes error messages name it.
Binding one way, both ways, and through expressions
target.bind(source) |
a.bindBidirectional(b) |
A listener | |
|---|---|---|---|
| Direction | From the source to the target | Both ways | Whatever your code does |
| Can the target be set? | No: it throws until unbind() |
Yes: the other side follows | Yes |
| Starting value | The source’s | b’s value is copied into a |
Unchanged |
| Typical use | Labels and sizes that show a value | A text field and a model field | Saving, logging, starting work |
Expressions build the values that bindings follow. Number properties have fluent methods, such as add, multiply,
greaterThan and asString. The Bindings class adds factories: Bindings.createStringBinding turns any code into a
binding with the dependencies you list, Bindings.when(condition).then(a).otherwise(b) chooses between two values,
Bindings.format fills a String.format pattern, and Bindings.size follows the length of a list.
The marks model as a graph of values; each arrow means "is computed from"
Text description of the diagram
The diagram shows five values of the marks model and arrows from each value to the values computed from it.
- theory and practical are IntegerProperty objects that the user sets.
- Arrows lead from both of them to total, a binding created with theory.add(practical).
- An arrow leads from total to grade, a binding created with Bindings.createStringBinding that turns the total into a letter.
- Arrows lead from total and from grade to summary, a StringProperty bound to Bindings.format, which writes both into one sentence.
When theory or practical changes, everything below it in the graph follows, and no listener copies a value.
A marks model, run without a window
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/** A marks model made of properties and bindings: no window, only the javafx.base module. */
public class Marks {
final IntegerProperty theory = new SimpleIntegerProperty(this, "theory", 52); // out of 70
final IntegerProperty practical = new SimpleIntegerProperty(this, "practical", 20); // out of 30
final NumberBinding total = theory.add(practical);
final StringBinding grade = Bindings.createStringBinding(() -> {
int marks = total.intValue(); // this example's scale: A from 80, B from 70, C from 55
return marks >= 80 ? "A" : marks >= 70 ? "B" : marks >= 55 ? "C" : "D";
}, total);
final StringProperty summary = new SimpleStringProperty(this, "summary");
Marks() {
summary.bind(Bindings.format("%d of 100, grade %s", total, grade));
}
public static void main(String[] args) {
Marks marks = new Marks();
System.out.println("theory 52, practical 20: " + marks.summary.get());
marks.theory.set(60);
System.out.println("theory 60: " + marks.summary.get());
try {
marks.summary.set("100 of 100, grade A");
} catch (RuntimeException e) {
System.out.println("summary.set(...) threw " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
StringProperty a = new SimpleStringProperty("typed in a");
StringProperty b = new SimpleStringProperty("typed in b");
a.bindBidirectional(b);
System.out.println("a.bindBidirectional(b): a = " + a.get() + ", b = " + b.get());
a.set("from a");
System.out.println("a.set(\"from a\"): b = " + b.get());
b.set("from b");
System.out.println("b.set(\"from b\"): a = " + a.get());
}
} from session import run, sdk
sdk("27")
run("""
java --module-path javafx-sdk-27/lib --add-modules javafx.base Marks.java
""") Output
$ java --module-path javafx-sdk-27/lib --add-modules javafx.base Marks.java
theory 52, practical 20: 72 of 100, grade B
theory 60: 80 of 100, grade A
summary.set(...) threw RuntimeException: Marks.summary : A bound value cannot be set.
a.bindBidirectional(b): a = typed in b, b = typed in b
a.set("from a"): b = from a
b.set("from b"): a = from b
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 marks_session.py
When theory changed from 52 to 60, the total, the grade and the summary followed in one step, and no line of main
recomputed them. Setting the bound summary threw a RuntimeException whose message starts with Marks.summary, the
bean and the name given to the property, which is the reason to give properties names. The two-way binding started by
copying b’s value into a: the property you call bindBidirectional on takes the other one’s value, so call it on
the side whose current value may be lost.
Listeners, and when to use one
JavaFX has three kinds of listeners for observable values and lists:
- An
InvalidationListeneris called when a value may have changed, with the observable and nothing else. - A
ChangeListeneris called with the old and the new value, and only when they differ. - A
ListChangeListenerreceives a description of what anObservableListadded, removed, replaced or reordered.
An observed value keeps each of its listeners reachable, with everything a listener refers to, until the listener is
removed. Code stops listening with removeListener, by wrapping the listener in a WeakChangeListener, or with the Subscription that subscribe
returns, whose unsubscribe() ends it.
GTU 3140705, unit 7
The syllabus of Object Oriented Programming-I lists property binding and “listeners for observable objects” in its JavaFX unit. This lesson covers both, and its exercise is a model of the kind a college registration form needs.
Prefer a binding whenever a value follows other values. A listener that copies values is a binding written by hand, and it can drift: a forgotten field, a missed event, or a value set before the listener was added.
Check yourself
Check yourself
4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
When each listener fires: a lazy binding traced event by event Pro: opens with a Pro, Premium or Ultimate pass
Bindings in JavaFX are lazy: they recompute only when someone reads them. This part counts every computation and every event for the same sequence of changes, so that you can predict what a listener will see.
In this part:
- Invalidation events, traced with a counter
- Change events make a binding eager
- A failure reproduced: the listener that ran once for three changes
2 runnable examples (Java, Python) · about 2 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
List changes, list bindings and a listener leak, reproduced Pro: opens with a Pro, Premium or Ultimate pass
Lists report changes in a richer form than single values, and listeners of every kind can keep objects alive by accident. This part reads list changes exactly as they arrive, then reproduces a leak and four ways to avoid it.
In this part:
- Reading a ListChangeListener’s changes in order
- Bindings that follow a list
- A closed panel kept alive by its listener, reproduced
2 runnable examples (Java, Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Exercise: a course-fee model made only of bindings Pro: opens with a Pro, Premium or Ultimate pass
A model whose derived values are all bindings can be tested without starting JavaFX, and it cannot drift. This exercise builds one for a registration form and checks it the same way.
In this part:
- The model and its rules
- Why these checks need no window
2 runnable examples (Java (FeeChecks.java), Java (FeeModel.java), Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
-
Fresher · Warm-up
What is the difference between bind() and bindBidirectional() in JavaFX?
bind() is one-way: target.bind(source) makes the target follow the source from then on, and the target can no longer be set; set() throws a RuntimeException until unbind() is called. It suits values that only show something, such as a label's text that follows a total. bindBidirectional() is two-way: a.bindBidirectional(b) first copies b's value into a, then keeps both in step whichever of them changes, and both can still be set. It suits a control and a model field that may each change, such as a text field and the name in a model. Each is undone by its own method, unbind() or unbindBidirectional(b).
-
Fresher to mid
An editor marks a document as unsaved through an InvalidationListener on a binding of the edit count; the listener only sets a dirty flag. The first edit sets the flag, but after a save clears it, later edits never set it again. Explain why, and fix it.
-
Mid to senior
A desktop app grows by about 4 MB each time a user opens and closes a report panel, and slows down after a long day. Each panel adds a ChangeListener to the application's theme property. Diagnose the leak, prove it, and fix it so that it cannot come back.
-
Fresher to mid
A form's Submit button may be enabled only when the name is not blank, the e-mail looks valid and the terms box is ticked. A teammate wrote three listeners that each recompute a flag and enable or disable the button. What can go wrong, and how would you rewrite and test it?
Key takeaways
- A JavaFX property holds a value and lets other code listen to it or bind to it; nodes expose all their state as properties.
bindmakes a property follow a source and forbids setting it;bindBidirectionalcopies the argument’s value first and then keeps both in step.Bindingsand the fluent methods build expressions such as totals, conditions and formatted text.- Invalidation listeners hear that a value may have changed; change listeners receive old and new values.
- Prefer a binding when a value follows other values, and stop listening when a short-lived object listens to a long-lived value.
References
- Package javafx.beans.property (JavaFX 27) (OpenJFX)
- Class Bindings (JavaFX 27) (OpenJFX)
- Interface ObservableValue (JavaFX 27) (OpenJFX)
- Interface Observable (JavaFX 27) (OpenJFX)
- Interface InvalidationListener (JavaFX 27) (OpenJFX)
- Interface ChangeListener (JavaFX 27) (OpenJFX)
- Class WeakChangeListener (JavaFX 27) (OpenJFX)
- Interface ObservableList (JavaFX 27) (OpenJFX)
- Class ListChangeListener.Change (JavaFX 27) (OpenJFX)
- Interface Subscription (JavaFX 27) (OpenJFX)
- Syllabus of Object Oriented Programming-I (subject code 3140705) (Gujarat Technological University)
Report a problem with this lesson
Kept only in this browser. Your Learn progress