The functionality of JavaFX ChangeListeners and InvalidationListeners is very similar. It can be hard to see a difference at all. Both types differ mainly in their runtime behavior. In fact, if performance matters, chosing the wrong type can destroy everything.
My suggestion is to stick to a simple rule: use a ChangeListener if you need to know the new value in the listener, otherwise use an InvalidationListener. In this article I will explain the reasons.
A couple of times I noticed code, where an InvalidationListener was used and the observed value was requested inside the listener. At first sight this may look like being the same as using a ChangeListener. Other times I noticed code where a ChangeListener was used, but neither old nor new value were used inside the listeners. Both approaches work, but they are not optimal from a performance perspective. To understand the reasons, we will first take a closer look at both types of listeners and their advantages and disadvantages.
A ChangeListener can be attached to any ObservableValue (e.g. JavaFX Properties) and it gets notified, if the value of the ObservableValue changes. An obvious advantage is, that old and new value are provided automatically in the method handler. But this advantage is rather minor and can be neglected. (I think, the main reason why both values are provided is, that they are available in the code calling the ChangeListeners and Swing developers are used to get them.) A more important advantage is, that a ChangeListener only gets fired, if the value REALLY changes. (InvalidationListener do not guarantee this, as we will see in a minute.) The disadvantage of a ChangeListener is, that it enforces eager calculation of bindings. Because we want to notify a ChangeListener only, if the value really changed, we have to calculate the new result of a binding immediately.
An InvalidationListener can be attached to any Observable (e.g. any ObservableValue is an Observable). It is notified when the Observable may have changed – there is no guarantee, that it really changed. The main advantage is, that the runtime does not have to compare old value and new value. In other words, bindings can still be calculated lazily, if an InvalidationListener is attached. The disadvantage is, that it may fire if the value did not really change.
With this explanation it should become obvious why the examples given above can result in performance issues. If you use a ChangeListener, but you do not need the new value in the listener, you are enforcing eager calculation of bindings, although it is not necessary. If you use an InvalidationListener, but read the new value inside the listener, you enforce eager calculation as well. This time it cannot be avoided, because you really need to know the new value anyway. But in addition your listener may be called far too often, even if the value did not really change and you do not need to take actions.
As stated in the beginning, my recommendation is to use a ChangeListener, if the new value is needed in the listener and to use an InvalidationListener otherwise. There is certainly more that you can do to improve performance, but I think this simple rule will get you a long way.