Builder Pattern for Java Records

Learn to implement the builder pattern style fluent API and copy constructors in Java records for creating immutable records with examples.

The builder pattern aims to provide a flexible solution for constructing complex data structures by separating the build process from the final representation of the data structure. We use the builder pattern to get a mutable intermediate variable and an immutable final result.

Since Java record types are immutable by default, the builder pattern is an excellent match for records.

1. Using Nested Builder

The following User record contains a nested Builder object that takes two mandatory arguments: id and name. The other two fields use the fluent API to set data. Finally, the build() method returns a fully built record instance.

public record User(long id, String name, String email, boolean status) {

  //Builder
  public static final class Builder {

    long id;
    String name;
    String email;
    boolean status;

    public Builder(long id, String name) {
      this.id = id;
      this.name = name;
    }

    public Builder email(String email) {
      this.email = email;
      return this;
    }

    public Builder status(boolean status) {
      this.status = status;
      return this;
    }

    public User build() {
      return new User(id, name, email, status);
    }
  }
}

We can use the Builder class as follows:

User user = new Builder(1L, "Lokesh").email("abc@domain.com").status(true).build();

Note that the record is still usable without the builder, but the builder provides an additional and flexible way to create a record instance.

2. Using Lombok @Builder

Lombok is a useful library for minimizing boilerplate code for features such as implementing the builder pattern in a class or record. Start by including the latest version of Lombok in the project. You can follow this detailed guide for Lombok installation.

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>

Use the @lombok.Builder annotation with the User record to add fluent API support.

import lombok.Builder;

@Builder
record User(long id, String name, String email, boolean status) { }

We can create the User instance step by step as follows:

User user = User.builder()
        .id(1l)
        .name("Lokesh")
        .email("email@domain.com")
        .status(true)
        .build();

3. Using RecordBuilder

The RecordBuilder is an opensource library for adding new features to the record types, such as:

3.1. Maven

Start with adding the RecordBuilder dependency in the project.

<dependency>
  <groupId>io.soabase.record-builder</groupId>
  <artifactId>record-builder-core</artifactId>
  <version>34</version>
</dependency>

Also, enable the annotation processing by adding record-builder-processor to annotation processor path.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.8.1</version>
  <configuration>
    <source>17</source>
    <target>17</target>
    <annotationProcessorPaths>
      <path>
        <groupId>io.soabase.record-builder</groupId>
        <artifactId>record-builder-processor</artifactId>
        <version>34</version>
      </path>
    </annotationProcessorPaths>
  </configuration>
</plugin>

3.2. @RecordBuilder Annotation

The annotation enables the fluent builder API in the annotated record type.

@RecordBuilder
record User(long id, String name, String email, boolean status) { }

This annotation generates a new class with a record name appended with “Builder”. For example, for User record, the generated builder class is UserBuilder.

User user = UserBuilder.builder()
        .id(1l)
        .name("Lokesh")
        .email("email@domain.com")
        .status(true)
        .build();

We can use the builder as copy constructor to create a new record from an existing record, after modifying only 1 or 2 fields. It copies the existing User‘s fields to a new User and modifies only the required fields.

User newUser = UserBuilder.builder(user)
        .email("new-email@domain.com")
        .status(true)
        .build();

The library also enables the ‘with’ methods for a single change in any record.

User inactiveUser = UserBuilder.from(user).withStatus(false);

4. Conclusion

In this Java tutorial, we learned to implement the builder pattern style fluent API for creating records in an incremental manner. we also learned to use the RecordBuilder library to implement copy constructors on the records.

Happy Learning !!

Sourcecode on Github

Comments

Subscribe
6 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.