Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Intermediate
  • 20 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Properties: values that announce their changes · Binding one way, both ways, and through expressions · A marks model, run without a window · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 13 minutes.

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

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.

theory and practical feed total; total feeds grade; total and grade feed the bound summary.theoryIntegerProperty, set by the userpracticalIntegerProperty, set by the usertotaltheory.add(practical)gradeBindings.createStringBindingsummary: StringProperty,bound to Bindings.format

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.

  1. theory and practical are IntegerProperty objects that the user sets.
  2. Arrows lead from both of them to total, a binding created with theory.add(practical).
  3. An arrow leads from total to grade, a binding created with Bindings.createStringBinding that turns the total into a letter.
  4. 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

Marks.java Java · Marks.java
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());
    }
}
The model, a bound property set by hand, and a two-way binding Python · marks_session.py
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 InvalidationListener is called when a value may have changed, with the observable and nothing else.
  • A ChangeListener is called with the old and the new value, and only when they differ.
  • A ListChangeListener receives a description of what an ObservableList added, 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.

  1. Question 1 of 4 The summary property of the marks model is bound. What happens when code calls summary.set("100 of 100")?

    Choose one answer.

    Show the answer to question 1

    Answer: It throws a RuntimeException with the message "Marks.summary : A bound value cannot be set."

    A bound property belongs to its binding. Call unbind() first if the property must be set by hand again. The message starts with the bean and the name given to the property's constructor, here Marks and summary.

  2. Question 2 of 4 a holds "x" and b holds "y". After a.bindBidirectional(b), what do they hold?

    Choose one answer.

    Show the answer to question 2

    Answer: Both hold "y", the value of the property passed as the argument

    bindBidirectional first copies the argument's value into the property you call it on, then keeps the two in step in both directions.

  3. Question 3 of 4 A label must always show the total of three fields. What fits best?

    Choose one answer.

    Show the answer to question 3

    Answer: Bind the label's text to an expression of the three fields

    A value that follows other values is a binding: it cannot drift, needs no code per field and keeps working when a fourth field joins the expression. Listeners are for actions that a change should trigger.

  4. Question 4 of 4 Which listener receives the old and the new value?

    Choose one answer.

    Show the answer to question 4

    Answer: ChangeListener

    A ChangeListener's changed method receives the observable value, the old value and the new one. An InvalidationListener receives only the observable, and a ListChangeListener receives a description of list changes.

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.

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.

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.

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

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

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

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

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

    Model answer, Pro. First step and follow-ups

  4. 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?

    Model answer, Pro. First step and follow-ups

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.
  • bind makes a property follow a source and forbids setting it; bindBidirectional copies the argument’s value first and then keeps both in step.
  • Bindings and 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

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.