One of the benefits of using JavaFX is its powerful and comprehensive support for properties and binding. Among other things, properties let you wire your scene so that the view updates automatically whenever you modify the data that sits behind it.

Properties are observable objects that may be writeable, or read-only. There are 30 types of Property object in JavaFX, including the StringProperty, SimpleListProperty and ReadOnlyObjectProperty. Each property wraps an existing Java object, adding functionality for listening and binding.

The SimpleDoubleProperty inherits from a significant part of the JavaFX bindings, properties and value classes
The SimpleDoubleProperty inherits from a significant part of the JavaFX bindings, properties and value packages. Each package adds one aspect of the eventual functionality that JavaFX’s properties exhibit.

In addition to the properties functionality, JavaFX provides functionality for binding values through Binding and Expression objects.

Binding is a mechanism for enforcing relationships between objects, in which one or more observable objects are used to update the value of another object. Bindings can act in one, or both directions and can be created either directly from properties (the Fluent API) or using the Bindings utility class (Bindings API).

Custom Bindings objects can also be created manually if you need extra customisation or performance. This is called the Low-Level API.

What will you achieve in this article?

Properties and binding are a group of interfaces and classes designed to make your life as a developer significantly easier. That being said, with 61 properties, and 249 methods in the Bindings class, it can get overwhelming and difficult to manage.

In this tutorial, I’ll go through:

A lot of issues early on with JavaFX like the Scene not updating itself when you change something stem from incorrectly wiring the scene with properties. The JavaFX scene is designed to update based on properties and events.

If you’re looking to deepen your understanding about how JavaFX works, check out the comprehensive guide on events too. These two articles combined will give you a really a solid foundation in the background workings of JavaFX.

What is a Property?

If you’re like me, and you don’t come from a computer science background, properties seem quite intimidating at first. There’s nothing magic under under the bonnet, though. Most of the JavaFX Property objects extend two key interfaces: ReadOnlyProperty<T> and WriteableValue<T>.

JavaFX simple property objects inherit from multiple interfaces, each of which requires one aspect of the property's functionality.

Some of them don’t, though. JavaFX has 10 read-only properties, which extend ReadOnlyProperty<T>, but don’t extend WriteableValue<T>.

Creating a property

JavaFX comes with ten in-built classes that make creating a property significantly easier. They implement all of the required functionality, from listening to binding.

  • SimpleBooleanProperty
  • SimpleDoubleProperty
  • SimpleFloatProperty
  • SimpleIntegerProperty
  • SimpleListProperty
  • SimpleLongProperty
  • SimpleMapProperty
  • SimpleObjectProperty
  • SimpleSetProperty
  • SimpleStringProperty

You can define any of the simple property objects either with or without an initial value. If defined without the default value, they’ll default to the default value of the object the property wraps – 0, false, “” or an empty collection.

SimpleIntegerProperty()
SimpleIntegerProperty(int initialValue)

They can also be created with a name, and an Object that JavaFX refers to as the property’s “bean”. This doesn’t encapsulate the property in any way, but creates a symbolic link to an object that represents the property’s “owner”.

SimpleIntegerProperty(Object bean, String name)
SimpleIntegerProperty(Object bean, String name, int initialValue)

Neither the name nor bean attributes changes the behaviour of the property, but it can act as a useful look-up. That’s useful if you’re attaching the same listener to multiple properties – especially properties generated programmatically. Then, once a change occurs, you can use the bean and name attributes to check which property just changed.

All JavaFX properties have methods to facilitate the following functions:

  • Listening for changes to the property’s value
  • Wring properties together (binding) so that they update automatically
  • Getting and setting (if writable) the property value

How to observe properties

As we just saw from above, JavaFX Property objects are a mish-mash of different implemented interfaces. That’s important here, because it means they provide two ways to listen for changes: invalidation and change.

JavaFX Property objects inherit from two interfaces which require functionality for change and invalidation listeners

Invalidation Listeners: Every property in JavaFX extends the Observable interface, meaning they all provide functionality to register listeners that fire when the property invalidates. If you’re not familiar with ‘invalidates’, it’s a way of marking a property as potentially changed without forcing it to recalculate the property’s value.

For properties with complex or expensive calculations, that can a useful tool, but I don’t find they’re used as much as change listeners.

Change Listeners: On top of that, JavaFX properties extend ObservableValue<T>, meaning you can register listeners that only fire when an object has actually changed. I’ve used these a lot more regularly than invalidation listeners.

Change listeners allow us to hear a change, and provide executable code ahead of time, which will execute based on the old and new values of the Property.

Listening for changes

You can register a listener on a property by invoking the addListener() method, providing either an InvalidationListener (less common) or ChangeListener (more common).

To add a change listener, we can define it fully by implementing the ChangeListener interface – a functional interface with one method: changed().

DoubleProperty altitude = new SimpleDoubleProperty(35000);
ChangeListener<Number> changeListener = new ChangeListener<Number>() {
        @Override
        public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
            if (newValue.doubleValue() < 15000) {
                deployParachute();
            }
        }
    };
altitude.addListener(changeListener);

All of the numerical properties (double, float, int and long) all require change listeners parameterized with a Number type. Of course, because they’re a functional interface, we can also create them in-line using lambdas since Java 8.

altitude.addListener((observable, oldValue, newValue) -> {
        if (newValue.doubleValue() < 15000) {
            deployParachute();
        }
});

Every time the value of the property changes, your change listener will fire once.

What is a binding?

Bindings are a way to wire your objects together, enforcing a relationship in which one object is dependant on at least one other. Properties can be bound natively themselves, as well as by creating Expression and Binding objects.

Expressions and Bindings are observable objects that also depend on the value of at least one other observable object (but potentially more). That enables you to create expression chains with multiple calculations: a super-simple way to string together string or number conversions.

Expressions and Bindings are in the Intermediate and Advanced parts of this article. For now, we’ll just bind two properties together without any additional classes.

How to bind properties

Behind the scenes, binding is a specific use-case of change-listening. All of JavaFX’s binding APIs have boilerplate code that listens for a change in (at least) one property and uses any change to update the value of that binding.

Where change listeners let us provide executable code ahead of time, binding gives us the convenience of stringing two properties together without having to worry about the implementation of updating a particular value.

The simplest and most heavily used are the methods that come attached to Property objects themselves: bind() and bindBidirectional(). These represent the simplest options for one-way and two-way bindings.

One-directional binding

When you invoke the bind() method on a target property, you pass it a second property as an argument – the binding source.

StringProperty sourceProperty = new SimpleStringProperty("First Value");
StringProperty targetProperty = new SimpleStringProperty("Second Value");
targetProperty.bind(sourceProperty);

Under the bonnet, the target stores a reference to the new source property and listens for changes. When the source value changes, it automatically updates the target (itself) when a change is detected.

One-directional binding copies the source to the target, and synchronises the target value with the source from that point forwards

In this case, targetProperty will track the value of sourceProperty. A few notes on extra bits for this method:

  • If the property is currently bound, the current binding is deleted and the new one replaces it
  • If a null argument is provided, the method throws a NullPointerExeption
  • The method immediately copies the value of the property it’s listening to, so the current value of the target property is lost.

Two-directional binding

It is, of course, possible to wire the properties together in both directions, linking their values together so that their values are always the same. To accomplish this, we invoke bindBidirectional(), passing the source property as an argument again.

StringProperty sourceProperty = new SimpleStringProperty("First Value");
StringProperty targetProperty = new SimpleStringProperty("Second Value");
targetProperty.bindBidirectional(sourceProperty);

If the values of the properties are different, then the order of the method call is important in determining the start value for the binding.

The method applied to targetProperty immediately updates the value of targetProperty before sourceProperty is bound reciprocally. That means that after a bidirectional binding, both properties will have the value of the property passed as an argument (the source property).

Bidirectional binding copies the source property's value to the target. Subsequent updates of property values operate in both directions.

In addition to the basic binding, which is limited to a carbon-copy approach, JavaFX supports more complex binding: creating multiple or complex manipulations of one property to update another.

If binding one property to another doesn’t cover your use case, don’t worry – I’ll go through more complex bindings in the following sections.

Intermediate binding techniques

There are three ways to manipulate any property and used that manipulated value for binding:

  • The Fluent API – methods like myProperty.bind(otherProperty).multiply(2)
  • The Bindings API – methods like Bindings.add(myProperty, otherProperty)
  • The Low-Level API – creating custom Bindings objects like DoubleBinding

Two of these provide cookie-cutter methods to beind properties in pre-defined implementations. I’ve always found these cover the majority of use cases for property binding, because they give you a huge amount of flexibilty.

The lower level API – creating Binding objects – can be higher performance, but can get a lot more complicated. Just on that basis, I’ve separated it off into it’s own section, which I’ll go into at the end of the article.

Fluent API

The Fluent API relies on the creation of “expression” objects, which are similar to properties (they’re observable values) with extra convenience methods to support extra manipulation.

Properties can also be bound to expressions, meaning the output of any manipulations can be used to update a property, just as above. That functionality – of being observable and depending on an object for a value – creates the possibility of chaining.

Expression chaining is useful for convenient repeated manipulation or combination or properties

In the case of Strings, we can use this to create chains of Strings, which are concatenated together. As soon as the sourceProperty is updated, the targetProperty will be updated automatically, via the expression.

StringProperty sourceProperty = new SimpleStringProperty("It doesn't matter how slowly you go");
StringExpression expression = sourceProperty.concat(" as long as you don't stop");

StringProperty targetProperty = new SimpleStringProperty("");
targetProperty.bind(expression);

System.out.println(targetProperty.get());
//Output: It doesn't matter how slowly you go as long as you don't stop

You can do all of this in-line, making complex code relatively concise. In this case, we’ll create the StringExpression while we call the bind method.

targetProperty.bind(sourceProperty.concat(" as long as you don't stop"));
System.out.println(targetProperty.get());
//Output: It doesn't matter how slowly you go as long as you don't stop

This can be a little confusing, but don’t forget targetProperty is actually bound to a StringExpression created by the concat() method. It’s the anonymous expression that gets bound to the sourceProperty.

This comes with amazing benefits. The API is rich, and the style produces readable code that covers the majority of manipulations you’ll need.

We can also use the Fluent API with numbers.

With numbers, we can chain manipulations to create simple, readable code that represents the formula we’re trying to replicate. To convert from degrees to radians, you multiply by Pi and divide by 180. The code is highly readable.

DoubleProperty degrees = new SimpleDoubleProperty(180);
DoubleProperty radians = new SimpleDoubleProperty();
radians.bind(degrees
                .multiply(Math.PI)
                .divide(180)
        );

However, in terms of performance, every expression is a link in a chain that needs to be updated on each change of the initial property. In this example, where we convert from degrees to radians, we’re creating two observable values just to update our radians property.

For complex transformations, or in situations where you’re doing a lot of binding, consider using the Bindings API (if it gives you the flexibility you need), or the Low-Level API.

The Bindings API

The Bindings class in JavaFX is a utility class containing 249 methods for property binding. It allows you to create bindings between observable objects of various types. You can combine properties with values, such a Strings and numbers, depending on the binding.

A DoubleBinding object can bind and listen to multiple observable objects and update whenever a change in any of them occurs

There are 10 general binding strategies, which I’ve wedged the two main areas I’ll call “operations on values” and “operations on collections”. Some don’t fit, so we have a graceless bucket called “other”.

Values

  • Mathematics (+, – ÷, ×)
  • Selecting the maximum or minimum
  • Value comparison (=, !=, <, >, <=, >=)
  • String formatting

Collections

  • Binding two collections (lists, maps, sets)
  • Binding values to objects at a certain position in a collection
  • Binding to collection size
  • Whether a collection is empty

Other bindings

  • Multiple-object bindings
  • Boolean operators (and, not or) – (and when!)
  • Selecting values

Because of the sheer volume of methods, I’ll go into depth on how to use methods that I think are (1) frequently used or (2) can be tricky to understand. Essentially, I’ll try to add value with useful explanations whilst trying not to give you RSI scrolling.

Operations on Values

The Bindings API provides support for four simple mathematical operations: addition, subtraction, multiplication and division. It provides separate methods for use of these with float, double, integer and long values, as well as between two ObservableNumberValue objects (for example, a DoubleProperty).

Mathematics
DoubleBinding	add(double op1, ObservableNumberValue op2)
NumberBinding	add(float op1, ObservableNumberValue op2)
NumberBinding	add(int op1, ObservableNumberValue op2)
NumberBinding	add(long op1, ObservableNumberValue op2)
DoubleBinding	add(ObservableNumberValue op1, double op2)
NumberBinding	add(ObservableNumberValue op1, float op2)
NumberBinding	add(ObservableNumberValue op1, int op2)
NumberBinding	add(ObservableNumberValue op1, long op2)
NumberBinding	add(ObservableNumberValue op1, ObservableNumberValue op2)

The same methods are available for each of the numerical options. The API swaps out the first and second arguments for convenience. For addition and multiplication that doesn’t make a difference (order doesn’t matter), but with subtraction, the order determines which argument is subtracted from the other.

DoubleBinding	subtract(double op1, ObservableNumberValue op2)
DoubleBinding	subtract(ObservableNumberValue op1, double op2)
NumberBinding	subtract(ObservableNumberValue op1, ObservableNumberValue op2)

In each case, the second argument is subtracted from the first.

DoubleBinding	multiply(double op1, ObservableNumberValue op2)
DoubleBinding	multiply(ObservableNumberValue op1, double op2)
NumberBinding	multiply(ObservableNumberValue op1, ObservableNumberValue op2)

Finally, with division, order becomes important again. The value of the binding is calculated as the first argument divided by the second argument.

DoubleBinding	divide(double op1, ObservableNumberValue op2)
DoubleBinding	divide(ObservableNumberValue op1, double op2)
NumberBinding	divide(ObservableNumberValue op1, ObservableNumberValue op2)

Overall, this generates a huge number of methods for relatively simple operations – 36 utility methods for four basic operations.

It might seem like over-kill, but it does the job it was intended to do. The creators of JavaFX put the legwork in so that you wouldn’t have to, providing implementations for each of the basic mathematical operations.

Selecting the maximum or minimum

As always, JavaFX does significant legwork in defining convenience methods with every situation in mind. Here’s the max() methods, which swap and change between observable and non-observable values.

max(double op1, ObservableNumberValue op2)
max(ObservableNumberValue op1, double op2)
max(ObservableNumberValue op1, ObservableNumberValue op2)

In fact, it’s exactly the same when you need to evaluate the minimum of two numbers. The binding will always equal the minimum value of two – at least one of which is observable.

min(double op1, ObservableNumberValue op2)
min(ObservableNumberValue op1, double op2)
min(ObservableNumberValue op1, ObservableNumberValue op2)
Value comparison (=, !=, <, >, <=, >=)

Value comparisons can be useful for automatically alerting a user when a list is full, enough items have been selected, or when they are winning or losing a game.

Negation and Not

In the simplest case, JavaFX provides methods to evaluate the negative (minus one times your number) and

negate(ObservableNumberValue value)
not(ObservableBooleanValue op)
Not Equal

Creeping up in complexity, JavaFX also provides for comparisons between two objects of various types. The value of the binding will always report whether the two objects are equal.

notEqual(double op1, ObservableNumberValue op2, double epsilon)
notEqual(Object op1, ObservableObjectValue<?> op2)
notEqual(ObservableNumberValue op1, double op2, double epsilon)
notEqual(ObservableNumberValue op1, ObservableNumberValue op2)
notEqual(ObservableNumberValue op1, ObservableNumberValue op2, double epsilon)
notEqual(ObservableObjectValue<?> op1, Object op2)
notEqual(ObservableObjectValue<?> op1, ObservableObjectValue<?> op2)

It also gives you lots of support for comparing strings. Especially worth a note is the notEqualIgnoreCase() methods. I spent a long time converting all of my strings to lowercase before comparison.

notEqual(ObservableStringValue op1, ObservableStringValue op2)
notEqual(ObservableStringValue op1, String op2)
notEqual(String op1, ObservableStringValue op2)
notEqualIgnoreCase(ObservableStringValue op1, ObservableStringValue op2)
notEqualIgnoreCase(ObservableStringValue op1, String op2)
notEqualIgnoreCase(String op1, ObservableStringValue op2)

As the heading suggested, our value comparison bucket also contains methods for comparing numerical values with less than and greater than. Check out the documentation for all the details – I’ll give you a break for now and move on to collections.

Operations on Collections

The Bindings API gives you four different ways to bind to a collection: carbon-copy, index-binding, size-binding and emptiness-binding. Only the first one copies the contents of your collection into a target collection. The other three extract values – single variables – based on one aspect of the collection’s state.

Binding two Collections

To bind two collections together, you can invoke either Bindings.bindContent(), or Bindings.bindContentBidirectional().

In the first case, you’ll be tracking an observable collection – an ObservableList, ObservableSet or ObservableMap – and creating a carbon-copy in a non-observable collection of the same type.

bindContent(List<E> list1, ObservableList<? extends E> list2)
bindContent(Map<K,V> map1, ObservableMap<? extends K,? extends V> map2)
bindContent(Set<E> set1, ObservableSet<? extends E> set2)

In this case, the content of the non-observable collection will immediately be overwritten with the contents of the observable one.

If you want to bind two observable collections together, use the Bindings.bindContentBidirectional() method, which takes two collections of the same type as an argument.

bindContentBidirectional(ObservableList<E> list1, ObservableList<E> list2)
bindContentBidirectional(ObservableMap<K,V> map1, ObservableMap<K,V> map2)
bindContentBidirectional(ObservableSet<E> set1, ObservableSet<E> set2)

In each of these cases the first collection will be erased and will take on the contents of the second list as part of the binding process.

The Bindings API doesn’t support any more complicated collection binding – things like reciprocal maps, range-limited list duplication. If you’re wanting these, you’ll need to create your own bindings. For that, check out the Low-Level API section below.

Binding collection-related values

Other than this, your options for binding are limited to binding values related to a collection – values at a certain index, the size of a collection, or whether the collection is empty.

Binding values to objects at a certain position in a Collection

It’s pretty easy to bind a variable based on a single value at a specified index in a collection. In each case, you need to provide an observable collection, and the index of the object.

The value of the index can also be passed in as an observable number value, meaning as it changes, so too does the value of the binding.

Each object type has a method specialization, which produces the right type of binding object for the value you’re requesting. Boolean, float, double, integer, long and string values are all supported through separate methods.

Remember: it’s definitely worth remembering these methods don’t bind your arguments together like we’ve seen above. Each of the valueAt methods returns a Binding object, which contains your value.

Binding to size or emptiness

Bindings based on size or whether a collection is empty are significantly easier. The Bindings API provides support for ObservableList, ObservableArray, ObservableSet and ObservableMap objects, as well as the ObservableStringValue.

Check out the docs for isEmpty() and the various size() methods to see all the options.

Custom Object Bindings – Multiple Objects and Complex Calculations

The last part of the Bindings API I’ll cover here is binding multiple, custom objects and providing a function to calculate the value.

createBooleanBinding(Callable<Boolean> func, Observable... dependencies)
createDoubleBinding(Callable<Double> func, Observable... dependencies)
createFloatBinding(Callable<Float> func, Observable... dependencies)
createIntegerBinding(Callable<Integer> func, Observable... dependencies)
createLongBinding(Callable<Long> func, Observable... dependencies)
createObjectBinding(Callable<T> func, Observable... dependencies)
createStringBinding(Callable<String> func, Observable... dependencies)

This is almost as good as the Low-Level API, but it has a few limitations:

  • Just beware, it depends on virtual method calls (you’re passing a function object rather than creating a class method). That means it may be slower than the Low Level API if you’re using it a lot.

It’s a relatively simple API, though – just pass in each of the dependencies and then convert them using the supplied function. I’ll define the function as a lambda for clarity, but you can also create a Callable<Double> and override the call() method manually too.

IntegerProperty cost = new SimpleIntegerProperty(15);
DoubleProperty multiplier = new SimpleDoubleProperty(25);
double flatRate = 4;

DoubleBinding totalCost = Bindings.createDoubleBinding(
        () -> cost.get() * multiplier.get() + flatRate,
        cost, multiplier
);

If that doesn’t suit your needs, you can create completely custom bindings by extending the objects available in JavaFX’s Bindings classes. This can be faster. So if you need that, here’s how.

Advanced Binding Techniques – Low-Level Binding

The most customisable way to create a binding in JavaFX is to manually create a Binding object yourself. The benefits of this are that you get to define exactly the calculations you want, and you don’t have to create chains of Expression objects, which can reduce performance.

Of course, you can also do complex calculations and bind multiple objects with the Bindings API, but as we saw above, that’s not quite as efficient. The benefit of the Low-Level API is that any calculations you need to perform when calculating the value is defined inside your custom Binding class.

If you want the technical terms for why this is likely to be better performing, it’s that class functions are more likely to be ‘in-lined’ by the compiler. That means your code will probably perform faster if you need the value of a binding to be computer repeatedly and quickly.

What is the Low-Level API?

The Low-Level API, a collection of 10 abstract Binding classes designed to implement all of the awkward bits of binding (adding and removing listeners, for example). That frees you to concentrate on specifying how the value of the binding should be calculated.

Each class takes observable values (like properties) and converts them into an output. Just like the Fluent and Bindings APIs, the Low-Level API provides support for boolean, string, numbers, collections and objects.

Creating a Low-Level Binding

Creating a Low-Level binding can be as simple as defining an abstract inner class (a class you define alongside other code). Because the abstract Bindings classes only have one abstract method, you only need to override computeValue() when you define the method.

As you define the binding, the official advice is to use an initialization block that binds up the source properties during binding creation. Honestly, I don’t like this as much as creating a constructor, but that’s probably just because initialization blocks look a little unfamiliar.

The overall effect is exactly the same – the compiler copies the code from initialization blocks into every constructor anyway. The constructor approach is more appropriate if you’re creating a concrete class you’re going to use more than once.

    //Inside your binding object at the top
    {
        super.bind(cost, itemCount);
    }

Then all that’s left is to define the computeValue() method. In this case, it’s pretty simple, but you can make the calculation as complicated as you want.

DoubleProperty cost = new SimpleDoubleProperty(25);
IntegerProperty itemCount = new SimpleIntegerProperty(15);

DoubleBinding totalCost = new DoubleBinding() {
    
    {
        super.bind(cost, itemCount);
    }
    
    @Override
    protected double computeValue() {
        return itemCount.get() * cost.get();
    }
};

From this point out, the value of the totalCost binding will always reflect the product of the cost and itemCount properties.

If you want to be able to pass the totalCost object around and retrieve the dependencies later, you can add extra functionality to override the default getDependencies() method.

Adding more functionality to the Low-Level bindings

On top of the customisable value method, each class in the Low-Level API can be extended by overriding the getDependencies() and dispose() methods.

  • getDependencies(): can return all of the dependencies if you need to store them and get them back later (see below for warnings!)
  • dispose(): can unregister listeners for all of a binding’s dependencies. You don’t usually need to do this unless you specifically implement the binding without weak listeners (the default).
Overriding getDependencies():

Overriding getDependencies() is useful if you want to be able to pass the bindings object to another class, or store it and retrieve the dependencies later.

DoubleBinding totalCost = new DoubleBinding() {

    {
        super.bind(cost, itemCount);
    }

    @Override
    protected double computeValue() {
        return itemCount.get() * cost.get();
    }

    @Override
    public ObservableList<?> getDependencies() {
        return FXCollections.observableList(Arrays.asList(cost, itemCount));
    }
};

It’s worth bearing in mind before you rush to override this method that all of the default implementations of the Low-Level API use weak listeners. That means:

If you’re using the Low-Level API with the default implementation, you need to keep strong references to your observable objects, otherwise they’ll be garbage collected and the reference will be lost.

That being said, if you’ve implemented the binding with strong listeners, you’ll want to override the dispose() method too. That’s going to prevent memory leaks that can occur if a binding isn’t unregistered from an observed object after its been used and forgotten about (at least by you…).

Overriding dispose():

Overriding the dispose() method is as simple as systematically unregistering each of the observable objects that we bound to start with. To do this in one call, you can invoke unbind(), passing in each of the values.

@Override
public void dispose() {
    unbind(cost, itemCount);
}

If you have a more complex custom implementation, you may need to look up and unregister observables one at a time.

Conclusions

Properties in JavaFX can be either read-only or writeable, but are always observable. Every property implements functionality from javafx.beans.binding, javafx.beans.value and javafx.beans.property packages.

Every property can be observed using InvalidationListener or ChangeListener objects. Both of these are accessed by invoking the addListener() method, because every property has an addListener() method for invalidation and for change.

An extension to property listening is property binding, a functionality that allows users to wire properties together so that they can be automatically updated based on one or more changes.

On top of that, JavaFX provides support for extending bindings through Expression objects and Bindings objects. It’s easiest to access these through the Fluent and Bindings APIs. However, if you absolutely need performance or customisation, the Low-Level API allows you to create completely custom bindings yourself.