Proposal: Deprecate Builders

Richard Bair richard.bair at oracle.com
Mon Mar 25 10:35:51 PDT 2013


We made a mistake. When we released JavaFX 2.0 we included a (large) set of *Builder classes to provide a builder-pattern approach to building JavaFX UIs. The builder-pattern approach provides several very nice features:
	- Ability to setup generic configuration once and "stamp out" multiple copies
	- Structured code style that closely approximates the "container hierarchy" of the UI
	- Strongly-typed "declarative" style programming

The Builders did come at a cost. There are a lot of them, and if used, require more class loading which slows down startup. When Pack200 is not being used, they represent a sizable fraction of the code base. They clutter up the JavaDoc (although this could be solved by having a "Builder" section like there is an "Enum" section and "Interface" section).

But all those advantages and costs were weighed and we concluded Builders were worth it, and they went in. Based on the above, I would still conclude that Builders are OK. However, it turns out our implementation has some intractable problems with respect to binary compatibility.

In order to ensure our Builders are always up-to-date with the latest API added to the core classes, the Builders are auto-generated using an annotation processor. Also, in order to keep the size of the builders small relative to the rest of the platform, we employ complex use of generics in order to allow the builders to inherit. That is, ControlBuilder extends from ParentBuilder extends from NodeBuilder just as Control extends from Parent extends from Node. But accomplishing this was no easy feat. Although the "id(String)" method is defined on NodeBuilder, it must return a ControlBuilder when used from the ControlBuilder. All of this was accomplished using generics, and as it turns out, depended on two bugs in JDK 6 and JDK 7 in order to work (although we didn't know it at the time). Those bugs were fixed in JDK 8, and as a result, the builders no longer work correctly for certain cases. Just about any option we take is going to lead to a binary incompatibility in 8, however we find this unacceptable. If we cannot avoid it, then whatever we choice we make for 8 had better be a final solution -- not something we will later decide we need to tweak further.

And this is what puts us in a tight spot. After having painfully looked over all of the alternatives, it seems to come down to this:
	a) Flatten the builder hierarchy so that ControlBuilder extends Object the same as ParentBuilder would and all other Builders.
	b) Stop using builders. Phase them out in a controlled manner.

Option (a) works because we remove all generics from the equation. There are some 73 or so methods on NodeBuilder. These would be redefined on the Builder for each class that extends from Node. This solution works by solving the root problem -- generic use in builder subclassing scenarios. This solution however changes the cost/benefit analysis for Builders, because now they would then comprise a much larger fraction of the overall platform size. And this runs at cross purposes with embedded use cases.

Option (b) works by removing Builders from the scenario entirely. Because this would be a *huge* breaking change, it would have to be managed carefully in a way that, in fact, won't break anybody (for at least several years). To implement this option, we would:
	1) Add @Deprecate to all Builders with a URL in the description to a page describing the change and how to migrate
	2) Stop auto-generating builders. We'd take whatever we had in the latest 2.x line and reuse them as-is.
		- This means that as new API is added to the platform, the builders wouldn't be updated to match
	3) Remove the Builders from the Java 8 JavaDoc
	4) Split the builders into a separate jar from jfxrt.jar. It would also be on the java.ext.libs path so that it is automatically picked up so that people are not broken when running FX 2.x apps on 8.x without modification. They'd still work.
	5) Provide a download link to the fx2-builders.jar containing all these builders.
	6) Encourage people to use fx2-builders.jar instead of relying on builders being on the class path by default
	7) In Java 9, remove the builders from the class path (this is several years down the road)

This proposal is essentially saying that the fix to make Builders work correctly reduces the value of the builders to the point where they just aren't worth it, and when considering Mobile / Embedded use cases, the extra cost of the builders is prohibitive. It also is based on the theory that the Builders are not being used by everybody, and so many people would not be negatively impacted but rather only positively impacted by no longer paying for the additional cost in bytes for the builders. It also recognizes that people who *are* using the builders and *do* like the programming style (including Jasper and many of our own) will see a loss of functionality that they have grown to like.

The other thing to bear in mind is that most of the benefits of Builders don't have to come from Builders -- there are alternatives. For example, the 'Ability to setup generic configuration once and "stamp out" multiple copies' benefit can also be achieved by helper methods.  The 'Structured code style that closely approximates the "container hierarchy" of the UI' advantage can be accomplished using FXML instead, or by using this pattern (which creates a boat-load of inner classes so it has its own problems):

// The first set of braces defines it as an anonymous subclass, the second set as an initializer
Group g = new Group() {{
    getChildren().add(new Button() {{
        setText("Hello");
    }});
}};

Another option which we've discussed a bit is whether we could use lambda's to do configuration in a builder-like style in Java 8, somewhat like Groovy does. So for example:

Group g = new Group(gg -> {
    gg.getChildren().addAll(new Button(b -> {
        b.setText("Hello");
    });
});

This approach doesn't generate the mass of anonymous inner classes that the other double-brace approach does, but accomplishes the same basic job of using builders for (more or less) declarative construction of a container-hierarchy in code.

So this is the long and the short of it. Eva can follow up with an in-depth post about the binary compatibility concerns if you wish. My proposal after having weighed the options is to phase out the Builders by deprecating them in 8 and removing them from the class path in 9. I believe that FXML or Lambda's or alternative languages all provide other avenues for achieving the same goals as builders but without the additional cost in byte codes or classes.

We have some cases where SceneBuilder is presently relying on the Builders, and we'd have to provide alternatives for that functionality it is presently using (such as annotations for constructor arguments).

Richard


More information about the openjfx-dev mailing list