Builders provide a nice, if verbose, way to create immutable java beans.
The upsides are a nice syntax for creating immutable beans, and a way to provide copy-on-write behaviour for immutable objects.
The downsides are the need for verbose boilerplate code, approximately doubling the code size of the bean source files, and the overhead of one extra object created and then left for garbage collection, for each created bean.
JavaBeans is a convention for data objects, that has existed since the beginning of time (which, in Java terms, is 1995).
A typical JavaBean looks like this:
public class Person { private String name; private Integer age; public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(Integer age) { this.age = age; } public Integer getAge() { return age; } }
The “set” and “get” methods form a pair. Each set/get pair, is seen as a “property” of the object.
The code for creating a JavaBean and set values, looks like this:
@Test void testCreate() { String name = "John Doe"; Integer age = 42; Person bean = new Person(); bean.setName(name); bean.setAge(age); assertEquals(name, bean.getName()); assertEquals(age, bean.getAge()); }
Sometime during the Java history (2005?) Java APIs started using fluent interfaces, making it possible to chain setter methods, similar to Smalltalk method cascading, giving object initialization a nicer looking syntax:
@Test void testCreate() { String name = "John Doe"; Integer age = 42; Person bean = new Person() .setName(name) .setAge(age); assertEquals(name, bean.getName()); assertEquals(age, bean.getAge()); }
The method chaining is accomplished by having the setters return a reference to the bean itself:
public class Person { private String name; private Integer age; public Person setName(String name) { this.name = name; return this; } public String getName() { return name; } public Person setAge(Integer age) { this.age = age; return this; } public Integer getAge() { return age; } }
However, in both the orginal JavaBeans and JavaBeans with fluent interfaces the resulting data objects are mutable.
In earlier days a bean was typically a datamodel backing some graphical GUI, and beans were thought of as database records.
But in the programs I have worked with recently, data models have changed from updating objects in memory, to being backed by a database, and updating the records of the database as soon as possible.
And usage of the beans have changed from an object that is updated and modified, to objects that are created once and then read from. After a database update, the original object is usually dropped and replaced with a new object created from a database query.
This means that the data objects can be changed to be immutable objects.
Immutable objects are inherently thread safe, and are also side effect free, and therefore can be freely accessed and used across threads.
The simplest way to make a JavaBean immutable, is to take away the setters (the backing fields are private, so they are only reachable by methods in the class, or by reflection):
public class Person { private String name; private Integer age; public String getName() { return name; } public Integer getAge() { return age; } }
But that leaves no way to create the object. So a constructor that initializes all fields is necessary:
public class Person { private String name; private Integer age; public Person(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public Integer getAge() { return age; } }
And if you try deserializing JSON objects into your bean using jackson (this happens by default both with JAX-RS support from jersey and with spring rest services), you’ll quickly discover that you also need to have a no-argument constructor (so that an instance easily can be made using reflection):
public class Person { private String name; private Integer age; public Person(String name, Integer age) { this.name = name; this.age = age; } public Person() { this(null, null); } public String getName() { return name; } public Integer getAge() { return age; } }
With only two fields, this isn’t too complicated, but with 6 or 10 or 15 fields, the constructor’s argument list gets long and complicated, especially for the case where you don’t wish to set all values, and provide null values instead:
@Test void testCreateOnlyAge() { Integer age = 42; Person bean = new Person(null, age); assertNull(bean.getName()); assertEquals(age, bean.getAge()); }
Enter the builder pattern. With the builder pattern, constructing an immutable object looks like this:
@Test void testCreate() { String name = "John Doe"; Integer age = 42; Person bean = Person.with() .name(name) .age(age) .build(); assertEquals(name, bean.getName()); assertEquals(age, bean.getAge()); }
And constructing an immutable object with only one value set looks like this:
@Test void testCreateNameOnly() { String name = "John Doe"; Person bean = Person.with() .name(name) .build(); assertEquals(name, bean.getName()); assertNull(bean.getAge()); }
This syntax is accomplished using an extra class, a so called “builder”.
The static Person.with() method returns an instance of Builder.
The Builder methods “name” and “age” are fluent setters of fields in the Builder class.
And the “build” method on the builder class, creates a Person instance, and copies values from the builder fields into the fields of the Person instance and finally returns the Person.
After the “build” method has been called, and the resulting value assigned, the builder is dropped, and left for garbage collection.
I.e. the builder only lives between the “with()” and the “build()” calls.
The complete code of the Person bean with a builder, is:
public class Person { private String name; private Integer age; public String getName() { return name; } public Integer getAge() { return age; } public static Builder with() { return new Builder(); } private Person() { } public static class Builder { private String name; private Integer age; public Person build() { Person bean = new Person(); bean.name = this.name; bean.age = this.age; return bean; } public Builder name(String name) { this.name = name; return this; } public Builder age(Integer age) { this.age = age; return this; } private Builder() { } } }
Both the Person class and the builder have private constructors, so using the Person.with().build() is the only way to create a Person.
The builder syntax for creating Person beans, is nice.
But cost of that nice syntax, is that the source file for the bean is close to twice the number of lines to the plain JavaBean examples.
Personally, I think the syntax for creating the bean is so nice, that the boilerplate code and line number increase is worth it.
But it is something to keep in mind, when you decide if this pattern is for you.
A builder can also help with copy-on-write.
That is: instead of modifying a bean, you copies the values from the old bean into a new one, at the same time you change some of the values, and then replace the use of the old bean with the new bean.
This is behaviour similar to what’s done with the spread operator in JavaScript, e.g.:
const a = { b: 'Hello', c: 42, d: 3.14, }; const d = { ...a, c: 43 }; console.log(d);
Object d will have the values in a, but with c replaced with the value set in the assignment to d.
The builder pattern can be modified to allow behaviour similar to the preceeding JavaScript example:
@Test void testCopyAndModify() { String name = "John Doe"; Integer age = 42; Person person = Person.with() .name(name) .age(age) .build(); Person agedPerson = Person.with(person).age(age + 1).build(); assertEquals(name, agedPerson.getName()); assertEquals(age + 1, agedPerson.getAge()); }
The “agedPerson” object is a copy of the “person” object, but with the age property modified.
The Person bean in this example, has an extra “with()” static method, that takes a bean as argument and creates a builder that is initialized from the values of the argument:
public class Person { private String name; private Integer age; public String getName() { return name; } public Integer getAge() { return age; } public static Builder with() { return new Builder(); } public static Builder with(Person person) { Builder builder = new Builder(); builder.name = person.name; builder.age = person.age; return builder; } private Person() { } public static class Builder { private String name; private Integer age; public Person build() { Person bean = new Person(); bean.name = this.name; bean.age = this.age; return bean; } public Builder name(String name) { this.name = name; return this; } public Builder age(Integer age) { this.age = age; return this; } private Builder() { } } }
The resulting syntax is very nice, but is even worse with respect to adding boilerplate code. And possibly error prone boilerplate code, at that.
What if we could automagically turn this:
@Getter @Builder public class Person { String name; Integer age; }
into this?
And indeed, with “project lombok“, we can. Lombok provides a set of tools that can autogenerate a builder and getters (and setters, if that’s desired).
But tempting as this solution is, it’s not something I will start using.
The reason is possible future complications in the build: one might be stuck with old build tools while the world moves forward in other places.
Today lombok supports two compilers, javac and ecj (the eclipse java compiler). And the support takes very different forms, according to their descriptions. If you want support for a different java compiler (e.g. gcj or clang), you’re out of luck.
Lombok supports the build tools maven, gradle, ant, and kobalt (three of which I know of, two of which I know well, and one I’ve never heard about). But if a new fantastic build tool appears, your project can’t use it until you’re able to create a lombok integration.
Lombok also requires plugins in the IDEs to avoid having the builder and getters show up as non-existing. I have only tried the eclipse plugin, and while it works well enough after installation, I needed to upgrade eclipse to be able to use the latest version of the plugin (and only a google search told me that). And it also required a number of eclipse restarts before it started working (at least 3 restarts after installation).
In contrast, beans with boilerplate builders and getters, will compile on any Java compiler, current or historical or future. And the beans with boilerplate are unaffected by the build tool used, and all methods and classes are visible in all IDEs and all versions of the IDEs current and future.
So, is there something similar in the works for standard Java?
And, the answer is: yes there is something similar in the works, but maybe not exactly what I want.
Starting with Java 14, there has been a preview of record types. The record types are simple, immutable, classes that behaves as values, rather than objects.
As a record type, the Person bean would look like this:
public record Person(String name, Integer age) { }
Stuff like toString() and equals() and hashCode() will be autogenerated.
And fields will be final and public (so: no getters).
But records are still only in preview, and records with many fields have the same issue as immutable beans without builders (constructor calls with many null parameters are unreadable and error prone).
So for the forseeable future I will continue to write my beans with builder boilerplate code.
I discovered a while back that rename of local variables in eclipse had stopped working, and today I eventually got around to dig into it.
Turns out this was caused by the lombok plugin. https://bugs.eclipse.org/bugs/show_bug.cgi?id=579168dbspy
I guess another reason not to use it, no matter how nice lombok’s behaviour is.
A comment in the bug, said that upgrading the lombok plugin from v1.18.22 to v1.18.24 made rename in eclipse work again, and I can confirm that upgrading the plugin fixed renaming for me as well.