JEP Proposal: Fluent Bindings for multiple observables
John Hendrikx
john.hendrikx at gmail.com
Tue Oct 28 08:28:13 UTC 2025
Thanks for taking a look Nir, I really appreciate it :)
On 26/10/2025 13:56, Nir Lisker wrote:
> When I need to combine observable values I usually do something like:
>
> Bindings.createDoubleBinding(() -> width.get() / height.get(),
> height, width);
>
> which is much less cumbersome than subclassing (although a bit less
> performant I think). It works for an arbitrary number of observables
> (including) observable lists/sets/maps too:
You're right, I forgot about the existence of the Bindings class, and
the helpers it has added for these cases. I barely use it since the
addition of fluent bindings. That version is a lot more compact and a
bit more workable.
>
> Bindings.createDoubleBinding(() -> list.getFirst() / height.get() *
> map.get("A"), height, list, map); // assume this makes sense somehow
>
> ReactFX, which is the go-to library for such extension, has a
> 'combine' method that work like this:
>
> Val.combine(height, width, (h, w) -> w / h);
I'm aware of this, and it is something to be considered as an addition.
It is similar to the argument where there is a `Subscription.combine`
and `subscription.and`, one being static and the other being a fluent
method. I think however we shouldn't be relying on ReactFX too much
anymore these days (I haven't used it in years now). The primary reason
for that is that ReactFX had to introduce new property classes which are
limited in how well they can interop with existing JavaFX code. With
the fluent bindings additions the need for ReactFX is limited. That is
not to say ReactFX doesn't offer anything interesting anymore :)
> To assess the proposal, I tried to write your JEP examples with the
> current JavaFX.
>
> Multi-stage chain (not sure how you mapped to a Point3D from a Point2D
> and a number):
>
> ObjectBinding<Point2D> point2d = Bindings.createObjectBinding(() ->
> new Point2D(x.get(), y.get()), x, y);
> ObjectBinding<Point3D> point3d = Bindings.createObjectBinding(() ->
> new Point3D(point2d.get().getX(), point2d.get().getY(), z.get()),
> point2d, z);
>
> Combining chains:
>
>
> ObjectBinding<Point2D> point1 = Bindings.createObjectBinding(() -> new
> Point2D(x.get(), y.get()), x, y);
>
> ObjectBinding<Point2D> point2 = Bindings.createObjectBinding(() -> new
> Point2D(x.get(), y.get()), x, y);
> ObjectBinding<Line2D> line = Bindings.createObjectBinding(() -> new
> Line2D(point1, point2), point1, point2); // Line2D is not a JavaFX class
>
>
> Using a default value:
>
> ObservableValue<Point2D> point1 = Bindings.createObjectBinding(() ->
> new Point2D(x.get(), y.get()), x, y).orElse(Point2D.ZERO);
>
>
> Some observations:
> Bindings returns a Binding rather than an ObservableValue and also has
> primitive specialization versions that have their unique methods (add,
> subtract...). These methods, however, can be replicated with fluent
> bindings and they also have the "known" subtle GC issue for the
> intermediary values.
Yes, I don't think we should cater to primitive specializations.
Bindings tend to be high level enough (often eventually tied to a UI)
that it makes little sense to "optimize" these at the cost of 8x more
variants. The GC issues are often insidious, and my problems in that
area have largely disappeared with the addition of the fluent bindings
and subscribe API's. This is why I'm hesitant to use API's from the
Bindings class, and why I think FX should offer alternatives in that area.
> Also, static imports can make these calls less ceremonious:
> createObjectBinding(() -> new Point2D(x.get(), y.get()), x, y);
Static imports are not really a plus for any argument in my view :)
Compare AssertJ and Hamcrest to see what I mean.
>
> The proposal is more ergonomic with its fluency for a couple of
> values, but I'm not sure it solves enough problems that the current
> mechanism can't.
It is an extension on the fluent API, it doesn't introduce anything that
isn't possible currently, just like `map` and `flatMap` didn't introduce
anything new, nor did `subscribe`. It is intended to be a more
discoverable, fluent and modern API, and to fill a gap where one has to
go from the fluent binding model to a static helper class model as soon
as you go from mapping just one source to needing two or more sources.
--John
>
> On Sun, Oct 26, 2025 at 11:59 AM John Hendrikx
> <john.hendrikx at gmail.com> wrote:
>
> JEP: https://gist.github.com/hjohn/611acb65769b68a845b8919c62a3e99a
>
> Hi everyone,
>
> I'd like to propose an extension to the fluent bindings API on
> ObservableValue (map, flatMap, orElse) which were introduced in JavaFX
> 19 over 3 years ago.
>
> The API currently is very powerful when dealing with a single
> observable, but lacks support when dealing with multiple observables.
> For example, let's say you want to compute a width/height ratio. You
> could write this:
>
> ObservableValue<Double> ratio = width.map(w -> w / height.get());
>
> ... but you'll quickly find that such an observable will not update
> itself when height changes, only when width changes.
>
> The go-to solution for this is currently:
>
> DoubleBinding ratio = new DoubleBinding() {
> { bind(width, height); }
>
> protected double computeValue() { return width.get() /
> height.get(); }
> }
>
> My proposal would extend ObservableValue with a new `with` method that
> returns an intermediate stage that can be easily converted back to an
> ObservableValue:
>
> ObservableValue<Double> ratio = width.with(height).map((w, h)
> -> w /
> h); // yields a ratio that updates whenever w or h changes
>
> Or for example:
>
> ObservableValue<Point> point = x.with(y).map(Point::new); //
> yields a Point that updates whenever x or y changes
>
> The intermediate stage would not be an observable value itself. This
> limits the API surface, and makes this proposal fairly lightweight and
> much easier to implement.
>
> Please see the JEP for the full proposal. I look forward to your
> feedback!
>
> --John
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20251028/ebc56e23/attachment-0001.htm>
More information about the openjfx-dev
mailing list