1. Overview

In this tutorial, we're going to look at the Lombok @Builder annotation to generate a builder for our class.

It is a handy annotation especially when we're dealing with classes that have many fields. In essence, it is a shortcut for applying the builder pattern.

2. Use @Builder on Class Level

When we annotate a class with @Builder, Lombok creates a builder for all instance fields in that class. 

We'll use the Student class:

@Builder
public class Student {

    private String name;
    private int age;
}

We've put the @Builder annotation on the class without any customization.

Let's look at the generated code:

public class Student {

    private String name;
    private int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static StudentBuilder builder() {
        return new StudentBuilder();
    }

    public static class StudentBuilder {

        private String name;
        private int age;

        StudentBuilder() {
        }

        public StudentBuilder name(String name) {
            this.name = name;
            return this;
        }

        public StudentBuilder age(int age) {
            this.age = age;
            return this;
        }

        public Student build() {
            return new Student(name, age);
        }

        public String toString() {
            return "Student.StudentBuilder(name=" + this.name + ", age=" + this.age + ")";
        }
    }
}

Lombok creates an inner static builder class named as StudentBuilder. This builder class handles the initialization of all fields in Student.

There are also other interesting points to note. Firstly, the Student class has a static builder() method to create a new builder. Then, we have a package-private constructor for Employee thus restricting the object creation. Also, the builder class creates an Employee instance with the build() method.

3. Use @Builder on Constructor Level

When we want to create a builder for specific fields, we should create a constructor with only those fields. Then when we put the @Builder annotation on the constructor, Lombok creates a builder class containing only the constructor parameters.

We'll work with the previous Student class:

public class Student {

    private String name;
    private int age;
    private String section;
    private String school;

    @Builder
    public Student(String section, String school) {
        this.section = section;
        this.school = school;
    }
}

We're adding two more fields here: section and school. Then we're creating a constructor with these fields and annotating the constructor with @Builder.

Let's look at the generated code:

public class Student {

    private String name;
    private int age;
    private String section;
    private String school;

    public Student(String section, String school) {
        this.section = section;
        this.school = school;
    }

    public static StudentBuilder builder() {
        return new StudentBuilder();
    }

    public static class StudentBuilder {

        private String section;
        private String school;

        StudentBuilder() {
        }

        public StudentBuilder section(String section) {
            this.section = section;
            return this;
        }

        public StudentBuilder school(String school) {
            this.school = school;
            return this;
        }

        public Student build() {
            return new Student(section, school);
        }

        public String toString() {
            return "Student.StudentBuilder(section=" + this.section + ", school=" + this.school + ")";
        }
    }
}

Lombok again creates the StudentBuilder class. However, the builder now doesn't contain the methods for name and age, since these fields aren't included in the constructor. Builder only sets the values for the section and school fields.

4. Use @Builder on Method Level

We can also annotate the methods with @Builder similar to the constructors. When placed on methods, the generated builder class includes fields that correspond to the method parameters.

public class Student {

    private final String name;
    private final int age;
    private String section;
    private String school;

    public Student(String name, int age, String section, String school) {
        this.name = name;
        this.age = age;
        this.section = section;
        this.school = school;
    }

    @Builder
    public static Student createStudent(String name, int age, String school) {
        return new Student(name, age, school, "");
    }
}

Here, Employee defines the createStudent method and we're annotating it with @Builder. As a result, the builder class will manage the parameters, name, age, and school:

public class Student {

    private final String name;
    private final int age;
    private String section;
    private String school;

    public Student(String name, int age, String section, String school) {
        this.name = name;
        this.age = age;
        this.section = section;
        this.school = school;
    }

    public static Student createStudent(String name, int age, String school) {
        return new Student(name, age, school, "");
    }

    public static StudentBuilder builder() {
        return new StudentBuilder();
    }

    public static class StudentBuilder {

        private String name;
        private int age;
        private String school;

        StudentBuilder() {
        }

        public StudentBuilder name(String name) {
            this.name = name;
            return this;
        }

        public StudentBuilder age(int age) {
            this.age = age;
            return this;
        }

        public StudentBuilder school(String school) {
            this.school = school;
            return this;
        }

        public Student build() {
            return Student.createStudent(name, age, school);
        }

        public String toString() {
            return "Student.StudentBuilder(name=" + this.name + ", age=" + this.age + ", school=" + this.school + ")";
        }
    }
}

5. Use @Singular and @Builder for Collection-Valued Fields

When we annotate a collection-valued field with @Singular, the builder doesn't generate a setter method. Instead, it generates two adder methods, one for adding a single item and one for adding another collection to the original collection. It also creates a clear method.

We'll work with the Student class:

@Builder
public class Student {

    private String name;
    private int age;
    @Singular
    private Set<String> lessons;
}

Here, we have the instance field lessons - Set<String>. Note that it is a collection-valued variable and annotated with @Singular. Since we're using @Builder on the class, the generated builder should honor the @Singular annotation:

public class Student {

    private String name;
    private int age;
    private Set<String> lessons;

    Student(String name, int age, Set<String> lessons) {
        this.name = name;
        this.age = age;
        this.lessons = lessons;
    }

    public static StudentBuilder builder() {
        return new StudentBuilder();
    }

    public static class StudentBuilder {

        private String name;
        private int age;
        private ArrayList<String> lessons;

        StudentBuilder() {
        }

        public StudentBuilder name(String name) {
            this.name = name;
            return this;
        }

        public StudentBuilder age(int age) {
            this.age = age;
            return this;
        }

        public StudentBuilder lesson(String lesson) {
            if (this.lessons == null) {
                this.lessons = new ArrayList<String>();
            }
            this.lessons.add(lesson);
            return this;
        }

        public StudentBuilder lessons(Collection<? extends String> lessons) {
            if (this.lessons == null) {
                this.lessons = new ArrayList<String>();
            }
            this.lessons.addAll(lessons);
            return this;
        }

        public StudentBuilder clearLessons() {
            if (this.lessons != null) {
                this.lessons.clear();
            }

            return this;
        }

        public Student build() {
            Set<String> lessons;
            switch (this.lessons == null ? 0 : this.lessons.size()) {
                case 0:
                    lessons = java.util.Collections.emptySet();
                    break;
                case 1:
                    lessons = java.util.Collections.singleton(this.lessons.get(0));
                    break;
                default:
                    lessons = new java.util.LinkedHashSet<String>(
                      this.lessons.size() < 1073741824 ? 1 + this.lessons.size() + (this.lessons.size() - 3) / 3 : Integer.MAX_VALUE);
                    lessons.addAll(this.lessons);
                    lessons = java.util.Collections.unmodifiableSet(lessons);
            }

            return new Student(name, age, lessons);
        }

        public String toString() {
            return "Student.StudentBuilder(name=" + this.name + ", age=" + this.age + ", lessons=" + this.lessons + ")";
        }
    }
}

Similar to previous examples, the builder contains methods for initializing the instance fields of Student. The important part is that we have three lesson related methods now: lesson, lessons, and clearLessons.

6. Default Values for @Builder

@Builder.Default allows us to define default values for the annotated fields.

@Builder
public class Student {

    private String name;
    private int age;
    @Builder.Default
    private String teacher = "Mrs. White";
}

The Student class declares the teacher field and initializes it with Mrs. White. As a result, if no value is supplied for teacher during the build phase, the builder will use Mrs. White as the default value.

7. Configure @Builder

Similar to other annotations, Lombok provides configuration properties for @Builder. We must place these properties in the lombok.config file.

7.1. lombok.builder.flagUsage

We can use the lombok.builder.flagUsage configuration key to prevent the usage of @Builder:

# [warning | error] (default: not set)
lombok.builder.flagUsage = error

There is no default value for this configuration, so it won't take effect until configured. In our case, we're setting it to error. So if Lombok detects usage of @Builder during compilation, it'll log an error message.

7.2. lombok.singular.useGuava

When we set lombok.singular.useGuava to true, Lombok uses Guava's immutable builders and types.

# [true | false] (default: false)
lombok.singular.useGuava = false

The default value for this configuration is false.

7.3. lombok.singular.auto

The lombok.singular.auto property controls whether Lombok should automatically find the singular name for the collection-valued field. For example, if we're using a common English plural like lessons, Lombok can infer the singular name as lesson and use it in the builder method names.

# [true | false] (default: true)
lombok.singular.auto = true

The default value for this configuration is true.

8. Conclusion

In this tutorial, we looked at how we can use the Lombok @Builder annotation to generate builders.

As always, the source code for all examples is available on Github.