Using Builders to Apply Overridable Default Values to Immutable Data Objects

Uri Bechar
Wix Engineering
3 min readDec 19, 2019

--

Using immutable data objects is a good practice as their state does not change after creation, are thread safe, and no unexpected modifications can occur while the object is passed around the application or between threads.

So let’s first create our immutable class, User with two properties name and email :

public class User {
private final String name;
private final String email;
public User(String name,String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}

As you can see the class members name and email are final and can be set only once in the class’s constructor. So this class’s object is set on construction and cannot be modified.

If we want the ability to add a default value we can set it in the constructor

public User(String name,String email) {
this.name = “John Doe”;
this.email = email;
}

But in the above example we cannot change the default value making the name argument redundant.

If we want to override this default value we can do the following:

public User(String name,String email) {
if(name != null) {
this.name = name;
}else{
this.name = “john Doe”;
}
this.email = email;
}
User user = new User(null, “myemail@mail.com”);

Let’s say we have a large data object with many properties for example an extended User class:

public User(String name, String email, String phone1, String phone2, gender Gender, Address address) {
this.name = “John Doe”;
this.email = email;
this.phone1 = phone1;
this.phone2 = phone2;
this.gender = gender;
this.address = address;
}

and we only want to change a single property value, we will have a constructor which will contain many nulls and will look something like this.

User user = new User(null, “myemail@mail.com”, null, null, null, null);

Not a pretty sight at all.

Let’s say we want to add a new property to the User data object this will change the constructor’s signature and will require to change the creation of the object all over the code, even if we have an overridable default value.

A much cleaner design is to use builders, builders are classes which hold a mutable intermediate state for creating immutable data objects. Let’s look at UserBuilder the builder for the User data object

public class User {
private final String name;
private final String email;
public User(String name,String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
@Override
public String toString() {
return “User{“ +
“name=’” + name + ‘\’’ +
“, email=’” + email + ‘\’’ +
‘}’;
}
public static class UserBuilder{
private String name = “John Doe”;
private String email = “guest@email.com”;
public UserBuilder withName(String name){
this.name = name;
return this;
}
public UserBuilder withEmail(String email){
this.email = email;
return this;
}
public User build() {
return new User(name, email);
}
}
}

Inside the user data object class we create static UserBuilder class, the members are set with the default values and as they are not final can be set with new values. New values are set using the “withXXXX” methods and return a reference to the builder with its new state”. Once values are set we call the build() method which creates our immutable User data object.

In the following example I only want to set the name property keeping the default value of the email property:

User user = new User.UserBuilder()
.withName(“Uri”)
.build();
System.out.println(user);

This will output:

User{name=’Uri’, email=’guest@email.com’}

Summary

The builder pattern enables a clean way of creating immutable data objects with default overridable properties.

Photo by Randy Fath on Unsplash

--

--