devxlogo

Beans Binding: A Java Data-Binding Solution with a Serious Problem

Beans Binding: A Java Data-Binding Solution with a Serious Problem

n the past couple of years, data binding has emerged as an important concept for GUI development in Java. Its main objective is to simplify the task of syncing a data object’s properties with their visual representations on the screen (text fields, tables, combo boxes, etc). JSR 295 (Beans Binding for Java, or simply Beans Binding) aims to deliver a default data-binding specification for the Java platform. Although JSR 295 is not yet part of the official JDK, you can download the reference implementation (at the time of writing, version 1.2.1). The last version was released in November 2007, with no new updates since. Although this signifies a mature and stable code base, a few issues prevent this library from being a perfect data-binding solution (more on that later).

Following a discussion of the types of properties that make up the building blocks of Beans Binding, this article walks you through binding object properties and collections, converting data between the input type and the destination property, and adding validators. It concludes with an examination of where the Beans Binding reference implementation falls short.

Beans Binding Building Blocks
The following are the fundamental types used by Beans Binding:

  • BeanProperty: This is one of two types of properties supported by Beans Binding. It defines a disconnected property definition for a JavaBean and a straightforward path to an object’s property. It can be defined as either a direct property of an object, like this:
    BeanProperty stringProperty = BeanProperty.create("text");

    Or as a nested property, referring to a child object’s property, like this:

    BeanProperty stringProperty = BeanProperty.create("pojo.text");

    BeanProperty is usually the best choice when dealing with two-way binding (i.e., when a change on either the source or target object should cause a data-binding synchronization).

  • ELProperty: This is a useful property that lets you embed an EL expression directly into the property definition, which enables you to create a binding to a collection of properties, for example:
    ELProperty  employeeFullNameProperty = ELProperty.create("${employee.firstName} ${employee.lastName}");

    You even can put Boolean conditional statements into the property definition, for example:

    ELProperty  isEmployeeActiveProperty = ELProperty.create("${employee.status > 0}");

    You also can use ELProperties for two-way data binding, but only if the objects contain a one-to-one property mapping expression, for example {employee.name}. For more complex expression (such as in the previous examples), you can use it for one-way data binding only (i.e., from source to target, but not the other way around).

  • UpdateStrategy: To provide you more control over your binding approach (two-way and one-way), Beans Binding uses the UpdateStrategy enum to define each type of binding being created. UpdateStrategy has the following three values, which are well explained in the source code itself:
    READ_ONCE/**An update strategy where the {@code AutoBinding} tries to sync the target from the source only once, at bind time.*/READ/**An update strategy where the {@code AutoBinding} tries to keep the target in sync with the source.*/READ_WRITE/**An update strategy where the {@code AutoBinding} tries to keep both the source and target in sync with each other.*/

Creating Bindings
Now that you’ve seen the basic building blocks, you can create some actual bindings between two object properties. All the magic happens in the Bindings class, which has a set of static methods that returns a Binding object:

MyPOJO pojo = new MyPOJO();JTextField field = new JTextField();//bind a POJO's first name to a JTextField's text propertyBeanProperty fNameProperty =    BeanProperty.create("firstName");BeanProperty textProperty =    BeanProperty.create("text");Binding

As you may have guessed, Bindings.createAutoBindings() is the important method; it takes all the pre-defined inputs and creates an actual binding definition for them. It is not active until you actually call bind() on it. That lets you define a binding and then customize the binding definition further, such as by adding custom converters or validators.

Converting Data
To convert data between the input type (usually just a String in a textbox or some other UI control) and the destination property (e.g., Date), you need to create a Converter object (passing it the source and target data types via generics) and then set it on your Bindings object. Here's an example:

dateBinding.setConverter(new Converter() {   @Override   public String convertForward(Date value) {      return dateFormat.format(value);   }   @Override   public Date convertReverse(String value) {      try {         return dateFormat.parse(value);      } catch (ParseException e) {         return Calendar.getInstance().getTime();      }   }});

Adding Validators
To add a validator, you need to extend the Validator object and in case of an error return a Result with the details:

dateBinding.setValidator(new Validator() {   @Override   public org.jdesktop.beansbinding.Validator.Result validate(Date value) {      if (value.getTime() > Calendar.getInstance().getTimeInMillis()) {         return new Result(null, "Date must be less than current date/time");      }      return null;   }});

Creating Observable Lists
The collections included in the default JDK are not observable, which means that adding or removing elements from them does not fire any events that would instruct Beans Binding to update the bound targets. Fortunately, Beans Binding ships with an ObservableCollections factory object that can create such events from existing, regular collections:

ObservableList employees =    ObservableCollections.observableList(      new ArrayList());

You would then use the observable lists for the actual binding of collections.

Using the Swing-Specific Binding Utilities
Many existing Swing controls do not provide the proper getters and setters in their APIs to easily bind JavaBeans to them. In order to work around this, Beans Binding provides a Swing-specific extension class called SwingBindings. It contains some additional methods that allow you to easily bind a list to a JTable, for example:

JTableBinding, JTable> bindings =    SwingBindings.createJTableBinding(      UpdateStrategy.READ_WRITE,       EmployeeDatabase.getAll(), employeesTbl);Property property =    BeanProperty.create("description"); ColumnBinding col = bindings.addColumnBinding(property);col.setColumnName("Employee Names");bindings.bind();

The preceding code binds a list of Employee POJOs (plain old Java objects) to a table, and shows one column (mapped to the description property) with an "Employee Names" column header. Similar methods exist in that class to bind to a JList or a JComboBox.

Binding to POJOs (Where Beans Binding Fails)
At this point you likely are ready to jump into Beans Binding. Alas, this library contains a major hidden flaw that makes it extremely cumbersome to use. The flaw is not the fault of Beans Binding itself, but rather the lack of required binding plumbing in the Java language. To be two-way bindable, each object on both the source and target sides must be a full JavaBean with manually-coded property change support—in other words, a simple POJO like this is not fully bindable out of the box:

public class Person  {   private String firstName;   private String lastName;   public String getFirstName() {      return firstName;   }   public void setFirstName(String firstName) {      this.firstName = firstName;   }   public String getLastName() {      return lastName;   }   public void setLastName(String lastName) {      this.lastName = lastName;   }}

The Person class is—at best—bindable one-way (as a target), but not two-way (as both a source and a target).

To be fully two-way bindable, the same POJO would need to look like this:

import java.beans.PropertyChangeSupport;import java.beans.PropertyChangeListener;public class Person  {   private String firstName;   private String lastName;   private PropertyChangeSupport support = new PropertyChangeSupport(this);   public void addPropertyChangeListener(PropertyChangeListener listener ) {      this.support.addPropertyChangeListener( listener );   }   public void removePropertyChangeListener(PropertyChangeListener listener ) {      this.support.removePropertyChangeListener( listener );   }   public String getFirstName() {      return firstName;   }   public void setFirstName(String firstName) {      String old = this.firstName;      this.firstName = firstName;      support.firePropertyChange("firstName",old,firstName);   }   public String getLastName() {      return lastName;   }   public void setLastName(String lastName) {      String old = this.lastName;      this.lastName = lastName;      support.firePropertyChange("lastName",old,lastName);   }}

All the additional lines of code are in bold type. As you can see, the main overhead is that on every setter you need to remember the current value and then fire a property change event with the property name in it. This overhead is already evident in this small POJO and only gets worse as the number of properties increases. Without the property change event, the Beans Binding library does not know that your object's property was updated, and accordingly, will not update any objects bound to it.

This flaw adds so many additional lines of code to each POJO that it makes Beans Binding practically useless. You would probably write about the same amount of code (or even less) by just syncing the properties manually in your UI.

This flaw has no simple workaround—other than enhancing the Java language with "real" properties that would automatically fire the events required for proper databinding. With Sun's misguided focus on JavaFX (leaving regular Java users behind), this is not likely to happen soon unfortunately.

Java ClassBuilder to the Rescue
Frustrated by this shortcoming, I created a small, open-source project called the Java ClassBuilder. It uses a custom classloader and dynamic bytecode transformation to generate the additional code automatically at run-time. All you have to do is add the predefined @Bindable annotation to your POJO, and you're done. Here's another fully-bindable version of the Person class:

@Bindablepublic class Person  {   private String firstName;   private String lastName;   public String getFirstName() {      return firstName;   }   public void setFirstName(String firstName) {      this.firstName = firstName;   }   public String getLastName() {      return lastName;   }   public void setLastName(String lastName) {      this.lastName = lastName;   }}

Of course, I hope that this is just a temporary solution.

Beans Binding Alternatives
As usual, no default library from Sun exists without some open source alternative attempting to improve on it. In this case, it is the JFace Databinding library from the folks at Eclipse. Despite the JFace moniker, this databinding library is actually quite generic and can be used with both Swing and SWT applications. It seems to enjoy a much more diverse and dynamic user community than Beans Binding (which hasn't seen a new release since November 2007), and it seems to be the future of data binding in Java. But that's a subject for a future article.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist