Java : Simple Builder Design Pattern

The builder pattern is one of the Gang of Four(GoF) design patterns. This article provides a simple builder pattern that is also used in the Java SE API, not the GoF builder pattern.

Note:


Introducation

The builder pattern is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming.

The Wikipedia above introduces the builder pattern of the GoF.

Class diagram(the GoF builder pattern)

The GoF pattern is also useful, but this article omits it. If you are interested, please check the above Wikipedia page.

Well, there are several classes and interfaces in the Java SE API that have Builder in their names.

  • DateTimeFormatterBuilder
  • Stream.Builder
  • HttpRequest.Builder
  • etc.

This article provides a simple builder pattern that is also used in the Java SE API. For convenience, this article refers to this pattern as "Simple Builder Pattern".

Simple Builder Pattern

Purpose

The purpose of the simple builder pattern is to :

  • A class that includes creating a complex object can be simplified.
  • For that purpose, you use a builder class that helps object creation.

A class with complex object creation is, for example, the class with many parameters in the constructor.

Example with many parameters

class Page {
    private final String title;
    private final String category;
    private final String publishedAt;
    private final String updatedAt;
    private final String content;

    Page(String title, String category,
         String publishedAt, String updatedAt, String content) {

        this.title = title;
        this.category = category;
        this.publishedAt = publishedAt;
        this.updatedAt = updatedAt;
        this.content = content;
    }

    @Override
    public String toString() {
        return """
                TITLE : %s
                CATEGORY : %s
                PUBLISHED : %s
                UPDATED : %s
                CONTENT : %s
                """
                .formatted(title, category, publishedAt, updatedAt, content);
    }
}

The Page class represents one page of a web site. It has 5 fields with "final".

Let's create a Page object.

final var page = new Page(
        "Today's weather is good!", "DIARY",
        "2100-03-20", "2100-04-01",
        "Today I went to a scenic park...");

System.out.println(page);

// Result
// ↓
//TITLE : Today's weather is good!
//CATEGORY : DIARY
//PUBLISHED : 2100-03-20
//UPDATED : 2100-04-01
//CONTENT : Today I went to a scenic park...

The Page class needs the following 5 parameters to create an object.

  1. title
  2. category
  3. publishedAt
  4. updatedAt
  5. content

In this example, the constructor has 5 parameters. It may still be easier to understand. However, the parameters increase to 6, 7, 8..., your code will be less readable and mistakes may be made.

You may feel anxious while coding.

  • What is the order of the parameters?
  • Which comes first, the publishedAt or the updatedAt?

If all the parameters are of different types, even if they are specified in the wrong order, you will get a compile error and you will notice immediately your mistake. However, if they contain the same type (e.g. String) as in this example, even if you get the order wrong, you will not get a compile error. It is during the test that you will notice your mistakes.

In programming, it is very important to find and solve problems as early as possible.


Example with optional parameters

Let's think that the publishedAt and updatedAt parameters can be omitted. If they are omitted, the default is "xxxx-xx-xx".

The constructor that omits those parameters can be overloaded as follows.

class Page {
    ...

    Page(String title, String category,
         String publishedAt, String updatedAt, String content) {
        ...
    }

    // Omit publishedAt and updatedAt.
    Page(String title, String category, String content) {
        this(title, category, "xxxx-xx-xx", "xxxx-xx-xx", content);
    }
...

However, the following two can't coexist.

  • A constructor omitting only the publishedAt parameter.
  • A constructor omitting only the updatedAt parameter.
class Page {
    ...

    // A constructor omitting only the publishedAt parameter.
    Page(String title, String category,
         String updatedAt, String content) {
        ...
    }

    // A constructor omitting only the updatedAt parameter. (Compile error!)
    Page(String title, String category,
         String publishedAt, String content) {
        ...
    }
...

To avoid this problem, default when null.

class Page {
    ...

    Page(String title, String category,
         String publishedAt, String updatedAt, String content) {

        this.title = title;
        this.category = category;
        this.publishedAt = publishedAt != null ? publishedAt : "xxxx-xx-xx";
        this.updatedAt = updatedAt != null ? updatedAt : "xxxx-xx-xx";
        this.content = content;
    }
...

You can specify defaults for the publishedAt and the updatedAt parameters. However, you need to specify null for parameters you want to omit. After all, the number of parameters to specify is still many.

final var page = new Page(
        "Today's weather is good!", "DIARY",
        null, null,
        "Today I went to a scenic park...");

System.out.println(page);

// Result
// ↓
//TITLE : Today's weather is good!
//CATEGORY : DIARY
//PUBLISHED : xxxx-xx-xx
//UPDATED : xxxx-xx-xx
//CONTENT : Today I went to a scenic park...
  • A class has many parameters in the constructor.
  • A class has optional parameters.

The simple builder pattern is useful in such cases.

What is the Builder class

In the simple builder pattern, a Builder class creates a target object.

Class diagram(simple builder pattern)

As an example, let's create a PageBuilder class that creates the Page class.

Class diagram(PageBuilder)

The PageBuilder class allows parameters except the title parameter to be omitted. This is to make the benefits easier to understand.

class PageBuilder {

    private String title;
    private String category = "None";
    private String publishedAt = "xxxx-xx-xx";
    private String updatedAt = "xxxx-xx-xx";
    private String content = "...";

    Page build() {
        if (title == null) {
            throw new IllegalStateException("title is null");
        }

        return new Page(title, category, publishedAt, updatedAt, content);
    }

    PageBuilder setTitle(String title) {
        this.title = title;
        return this;
    }

    PageBuilder setCategory(String category) {
        this.category = category;
        return this;
    }

    PageBuilder setPublishedAt(String publishedAt) {
        this.publishedAt = publishedAt;
        return this;
    }

    PageBuilder setUpdatedAt(String updatedAt) {
        this.updatedAt = updatedAt;
        return this;
    }

    PageBuilder setContent(String content) {
        this.content = content;
        return this;
    }
}

The PageBuilder class has 5 fields for creating a Page object. Except the title parameter it is initialized with the default value.

class PageBuilder {

    private String title;
    private String category = "None";
    private String publishedAt = "xxxx-xx-xx";
    private String updatedAt = "xxxx-xx-xx";
    private String content = "...";

...

Next, you create setter methods to set the value. The method returns the PageBuilder itself for convenience.

class PageBuilder {
    ...

    PageBuilder setTitle(String title) {
        this.title = title;
        return this;
    }

    PageBuilder setCategory(String category) {
        this.category = category;
        return this;
    }

...

Finally, you create the build method for creating a Page object. The title field is required, so check it.

class PageBuilder {
    ...

    Page build() {
        if (title == null) {
            throw new IllegalStateException("title is null");
        }

        return new Page(title, category, publishedAt, updatedAt, content);
    }

...

Let's use the PageBuilder class!

final var builder = new PageBuilder();

builder.setUpdatedAt("2100-03-20");
builder.setTitle("Today's weather is good!");
builder.setCategory("DIARY");

final var page = builder.build();

System.out.println(page);

// Result
// ↓
//TITLE : Today's weather is good!
//CATEGORY : DIARY
//PUBLISHED : xxxx-xx-xx
//UPDATED : 2100-03-20
//CONTENT : ...

First, you create a PageBuilder object.

final var builder = new PageBuilder();

Next, you set the required parameters.

builder.setUpdatedAt("2100-03-20");
builder.setTitle("Today's weather is good!");
builder.setCategory("DIARY");

The calling order of setter methods is free. If you want to omit the parameters, you don't need to call setter methods.

Finally, you create a Page object with the build method.

final var page = builder.build();

The setter methods return the Builder object itself, so you can chain to call the methods.

final var page = new PageBuilder().setUpdatedAt("2100-03-20")
        .setTitle("Today's weather is good!")
        .setCategory("DIARY").build();

System.out.println(page);

This is the Simple Builder Pattern that is also used in the Java SE API. Let’s compare without/with the Simple Builder Pattern.

Without Builder With Builder
final var page = new Page(
        "Today's weather is good!", "DIARY",
        null, "2100-03-20", null);

System.out.println(page);

// Result
// ↓
//TITLE : Today's weather is good!
//CATEGORY : DIARY
//PUBLISHED : xxxx-xx-xx
//UPDATED : 2100-03-20
//CONTENT : ...
final var page = new PageBuilder()
        .setTitle("Today's weather is good!")
        .setCategory("DIARY")
        .setUpdatedAt("2100-03-20")
        .build();

System.out.println(page);

// Result
// ↓
//TITLE : Today's weather is good!
//CATEGORY : DIARY
//PUBLISHED : xxxx-xx-xx
//UPDATED : 2100-03-20
//CONTENT : ...

Do you feel that it is easier to understand which parameter is what with the simple builder pattern? If so, you have benefited from the simple builder pattern.


Specify required parameters in the constructor

The PageBuilder class above checked the required title parameter in the build method.

class PageBuilder {
    ...

    Page build() {
        if (title == null) {
            throw new IllegalStateException("title is null");
        }

        return new Page(title, category, publishedAt, updatedAt, content);
    }

...

If there are few required parameters, it is also recommended to specify them in the constructor of the Builder class.

class PageBuilder {

    private final String title; // ★ Add final keyword
    private String category = "None";
    private String publishedAt = "xxxx-xx-xx";
    private String updatedAt = "xxxx-xx-xx";
    private String content = "...";

    // ★ Specify a title parameter
    PageBuilder(String title) {
        if (title == null) {
            throw new IllegalArgumentException("title is null");
        }

        this.title = title;
    }

    Page build() {
        return new Page(title, category, publishedAt, updatedAt, content);
    }

    // ★ Remove the setTitle method.

    PageBuilder setCategory(String category) {
        this.category = category;
        return this;
    }

    PageBuilder setPublishedAt(String publishedAt) {
        this.publishedAt = publishedAt;
        return this;
    }

    PageBuilder setUpdatedAt(String updatedAt) {
        this.updatedAt = updatedAt;
        return this;
    }

    PageBuilder setContent(String content) {
        this.content = content;
        return this;
    }
}
final var builder = new PageBuilder("Today's weather is good!");

builder.setUpdatedAt("2100-03-20");
builder.setCategory("DIARY");

final var page = builder.build();

System.out.println(page);

// Result
// ↓
//TITLE : Today's weather is good!
//CATEGORY : DIARY
//PUBLISHED : xxxx-xx-xx
//UPDATED : 2100-03-20
//CONTENT : ...

Required parameters are specified in the constructor. So the build method does not need to check for them. However, please be careful if there are many required parameters. If all of them are specified in the constructor, it will be a class that includes creating a complex object.

Disadvantages

The simpler builder pattern can be a disadvantage if you don't use it in the suitable cases.

The builder pattern needs to create a Builder class. The amount of code may increase because an object is created via the Builder class. More code means more time for your coding.

  • Write concise code without the builder.
  • Write readable code with the builder.

Which one is better will depend on the number of required parameters and complexity. You may want to make good choices.


Example with Java SE API

There are several APIs in the Java SE APIs that employ the simple builder pattern. For example, HttpRequest.Builder, DateTimeFormatterBuilder, Stream.Builder, etc.

HttpRequest.Builder

A builder of HTTP requests.

The HttpRequest.Builder interface is a builder for creating HttpRequest objects. The HttpRequest class represents a HTTP request.

final var builder = HttpRequest.newBuilder(URI.create("https://example.com/"));

final var request = builder
        .POST(HttpRequest.BodyPublishers.ofString("abcd"))
        .header("Content-Type", "text/plain; charset=UTF-8")
        .timeout(Duration.ofSeconds(30))
        .build();

System.out.println(request);

// Result
// ↓
//https://example.com/ POST

The HttpRequest.newBuilder method returns a builder object. The newBuilder method specifies a URI as the required parameter. Next, you set optional parameters to the builder object. Finally, the build method creates a HttpRequest object.

DateTimeFormatterBuilder

Builder to create date-time formatters.

The DateTimeFormatterBuilder class is a builder for creating DateTimeFormatter objects. The DateTimeFormatter class formats date-time objects.

First, let's look at an example without the builder.

final var date = LocalDate.of(2100, 12, 3);
final var formatter = DateTimeFormatter.ofPattern("'year='y 'month='M 'day='d");

final var str = date.format(formatter);
System.out.println(str); // year=2100 month=12 day=3

The formatter is simply created with the DateTimeFormatter.ofPattern method. However, you must understand in advance what symbols such as 'y', 'M', and 'd' mean.

Next, an example with the builder.

final var builder = new DateTimeFormatterBuilder()
        .appendLiteral("year=").appendValue(ChronoField.YEAR)
        .appendLiteral(" month=").appendValue(ChronoField.MONTH_OF_YEAR)
        .appendLiteral(" day=").appendValue(ChronoField.DAY_OF_MONTH);

// Build a DateTimeFormatter.
final var formatter = builder.toFormatter();

final var date = LocalDate.of(2100, 12, 3);

final var str = date.format(formatter);
System.out.println(str); // year=2100 month=12 day=3

The amount of code to write has increased a little. However, you don't need to understand symbols such as 'y', 'M', 'd'. I think it's easier for people who don't know symboles to understand the code.

I'm not sure which is better DateTimeFormatter.ofPattern or DateTimeFormatterBuilder. The concise code by ofPattern is also attractive.

Note

Keyword argument

Some programming languages except Java allow specification of method parameters in any order. For example, Python keyword argument can be :

def func(x, y, z):
    print(f'x={x} : y={y} = z={z}')


func(z=10, y=20, x=30)

// Result
// ↓
//x=30 : y=20 = z=10

In such programming languages, the advantage of the simple builder pattern may be small.

Conclusion

The purpose of the simple builder pattern is to :

  • A class that includes creating a complex object can be simplified.
  • For that purpose, you use a builder class that helps object creation.

Cases that may benefit from this include :

  • A class has many parameters in the constructor.
  • A class has optional parameters.

In such a case, you may want to consider the simple builder pattern.


Related posts

To top of page