We are still cooking the magic in the way!
The Observer Pattern in JavaFX
The Observer Pattern in JavaFX
The Observer pattern is the backbone of every reactive UI framework: when a piece of data changes, all interested parties are notified automatically — without any of them polling for updates. JavaFX bakes this pattern directly into its property system through two complementary listener types: InvalidationListener and ChangeListener. Knowing which one to reach for, and understanding exactly when each fires, is a critical skill for writing efficient JavaFX applications.
Why Two Listener Types?
JavaFX properties are lazy by design. A computed binding does not recalculate its value the moment a dependency changes; instead, it marks itself as invalid and defers the recomputation until someone actually asks for the value. This laziness is the performance model of the entire property system, and the two listener types reflect that two-stage lifecycle:
- InvalidationListener — fires the instant a property transitions from valid to invalid. The new value has not been computed yet. Use it when you only need to know that something changed, not what it changed to.
- ChangeListener — fires only when a property actually delivers a new value (i.e., after the lazy recomputation). It receives the old value and the new value. Use it when you need to act on the specific values involved in the transition.
TextField — the distinction rarely matters because getValue() is always called immediately. For long dependency chains of computed bindings, InvalidationListener can avoid cascading recalculations.
InvalidationListener in Practice
The InvalidationListener functional interface has a single method: invalidated(Observable observable). You attach it with addListener(InvalidationListener) and remove it with the matching removeListener.
Notice that setting the property to the same value a second time (set(1) twice) does not fire the listener. JavaFX suppresses the invalidation if the value is unchanged — this is important to remember when you expect a notification that never arrives.
get() inside an InvalidationListener, the property revalidates immediately and the listener will fire again on the next change. If you never call get(), subsequent changes to the same value will not re-fire the listener until it has been re-read at least once. This is the "once-invalid, stay-silent" behaviour of the lazy model.
ChangeListener in Practice
The ChangeListener<T> functional interface exposes changed(ObservableValue<? extends T> obs, T oldValue, T newValue). Internally, JavaFX calls get() on the property before delivering the event, which forces evaluation and guarantees you receive the actual before/after values.
A Realistic UI Example: Live Form Validation
In a real application you attach ChangeListener to a TextField's textProperty() to give the user immediate feedback. The old value lets you implement "undo-last-change" logic if needed.
Choosing Between the Two
- Use InvalidationListener when: you only need to trigger a side-effect (e.g., mark a cache dirty, schedule a redraw) and do not need to know the specific before/after values. It is cheaper because it avoids forcing evaluation of the property.
- Use ChangeListener when: you need the old or new value, or you need to perform comparisons, logging, or validation that depends on the actual values.
- For UI controls where the user reads and writes the value constantly, prefer
ChangeListener— the overhead of recomputing a simple property is negligible, and the code is clearer.
Avoiding Memory Leaks: Weak Listeners
Adding a listener creates a strong reference from the property to the listener (and often indirectly to the controller or model object). If you add a listener but never call removeListener, the property holds the listener alive for as long as the property itself lives — which in a long-running application can be indefinitely.
JavaFX provides WeakInvalidationListener and WeakChangeListener in the javafx.beans package. They wrap your listener in a WeakReference; the property automatically cleans them up once the underlying listener is garbage-collected.
WeakInvalidationListener without storing the strong reference anywhere, the GC may collect it on the very next collection cycle and your listener will silently stop firing. Always hold a strong reference to the underlying listener in a field of the enclosing object.
Summary
The JavaFX Observer pattern gives you two precise hooks into the property lifecycle. InvalidationListener fires early and cheaply at the "something might have changed" moment. ChangeListener fires later with guaranteed before/after values. Use weak variants for long-lived properties in controllers and models to keep memory clean. In the next lesson you will see how to apply this knowledge when building custom reusable controls that expose their own observable state.