In this tutorial, we’re going to take a close look at how to use Lombok Builder annotation to implement the Builder pattern in Java.

First, we’ll start with a simple example, demonstrating how to design the builder pattern without using the Lombok library.

Then, we’re going to showcase how to use the @Builder annotation to reduce all the boilerplate code.

You’ll be amazed, believe me! So, let’s get started!

Implement Builder Pattern without Lombok

In short, the builder is a creational pattern designed to simplify the logic of creating complex objects.

We can use this pattern, for example, to create objects with too many fields.

Let’s see how we can implement the builder pattern in Java. For instance, consider the following Employee class:

    
        public class Employee {
            private String csn;
            private String firstName;
            private String lastName;
            private double salary;
            Employee(String csn, String firstName, String lastName, double salary) {
                this.csn = csn;
                this.firstName = firstName;
                this.lastName = lastName;
                this.salary = salary;
            }
            public static class EmployeeBuilder {
                private String csn;
                private String firstName;
                private String lastName;
                private double salary;
                EmployeeBuilder() {
                }
                public Employee.EmployeeBuilder csn(String csn) {
                    this.csn = csn;
                    return this;
                }
                public Employee.EmployeeBuilder firstName(String firstName) {
                    this.firstName = firstName;
                    return this;
                }
                public Employee.EmployeeBuilder lastName(String lastName) {
                    this.lastName = lastName;
                    return this;
                }
                public Employee.EmployeeBuilder salary(double salary) {
                    this.salary = salary;
                    return this;
                }
                public Employee build() {
                    return new Employee(this.csn, this.firstName, this.lastName, this.salary);
                }
            }

            public static Employee.EmployeeBuilder builder() {
                return new Employee.EmployeeBuilder();
            }
        }
    
    

As we can see, we need to create a fully new static nested class and copy all the properties from the Employee class.

I know what you’re thinking brother: boilerplate code everywhere ;)

And yeah, this is exactly where the Lombok builder annotation comes into the picture.

Lombok Builder Example

In this chapter, we’re going to demonstrate the magic of the @Builder annotation. We’ll see how we can use it and what features bring in.

You can also check our article about how to use @Data annotation.

First things first, we need to add the Lombok dependency to our pom.xml:

    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
     

Use @Builder at the Class level

Lombok provides a straightforward way to generate a builder to create instances of our classes.

Believe it or not! The only thing we need to do is annotate our User class with the Lombok @Builder, nothing else:

    
        @Builder
        public class User {
            private int id;
            private String firstName;
            private String middleName;
            private String lastName;
            private String address;
            private String phoneNumber;
            private int age;
            private String email;
            private String function;
            private String organization;
            private String login;
            private String password;
        }
    
    

Lombok will do all the heavy liftting for us. Let’s de-lombok and take a look at the generated code:

    
        public class User {
        // private fields are ommitted
        
        User(final int id, final String firstName, final String middleName, final String lastName, final String address, final String phoneNumber, final int age, final String email, final String function, final String organization, final String login, final String password) {
            this.id = id;
            this.firstName = firstName;
            this.middleName = middleName;
            this.lastName = lastName;
            this.address = address;
            this.phoneNumber = phoneNumber;
            this.age = age;
            this.email = email;
            this.function = function;
            this.organization = organization;
            this.login = login;
            this.password = password;
        }
        public static class UserBuilder {
            private int id;
            private String firstName;
            private String middleName;
            private String lastName;
            private String address;
            private String phoneNumber;
            private int age;
            private String email;
            private String function;
            private String organization;
            private String login;
            private String password;
            
            UserBuilder() {
            }
            
            public User.UserBuilder id(final int id) {
                this.id = id;
                return this;
            }
            public User.UserBuilder firstName(final String firstName) {
                this.firstName = firstName;
                return this;
            }
            public User.UserBuilder middleName(final String middleName) {
                this.middleName = middleName;
                return this;
            }
            public User.UserBuilder lastName(final String lastName) {
                this.lastName = lastName;
                return this;
            }
            public User.UserBuilder address(final String address) {
                this.address = address;
                return this;
            }
            public User.UserBuilder phoneNumber(final String phoneNumber) {
                this.phoneNumber = phoneNumber;
                return this;
            }
            public User.UserBuilder age(final int age) {
                this.age = age;
                return this;
            }
            public User.UserBuilder email(final String email) {
                this.email = email;
                return this;
            }
            public User.UserBuilder function(final String function) {
                this.function = function;
                return this;
            }
            public User.UserBuilder organization(final String organization) {
                this.organization = organization;
                return this;
            }
            public User.UserBuilder login(final String login) {
                this.login = login;
                return this;
            }
            public User.UserBuilder password(final String password) {
                this.password = password;
                return this;
            }
            public User build() {
                return new User(this.id, this.firstName, this.middleName, this.lastName, this.address, this.phoneNumber, this.age, this.email, this.function, this.organization, this.login, this.password);
            }
            @Override
            public String toString() {
                return "User.UserBuilder(id=" + this.id + ", firstName=" + this.firstName + ", middleName=" + this.middleName + ", lastName=" + this.lastName + ", address=" + this.address + ", phoneNumber=" + this.phoneNumber + ", age=" + this.age + ", email=" + this.email + ", function=" + this.function + ", organization=" + this.organization + ", login=" + this.login + ", password=" + this.password + ")";
            }
        }
        public static User.UserBuilder builder() {
            return new User.UserBuilder();
        }

    }
    
    

Amazing right? Lombok automatically generates all the boilerplate code required by the Builder pattern.

As shown above, the default name of the builder class is UserBuilder. However, we can easily change the name, to do that we need to use: @Builder(builderClassName = “name goes here”).

The same thing applies to the build() method, we can use @Builder(buildMethodName = “create”) to override the default name.

@Builer annotatioon provides many attributes, you can check them here for further information.

Now, we can construct User objects using the Builder approach:

    
        User user01 = User.builder()
                .firstName("Jean")
                .lastName("Collins")
                .build();
        User user02 = User.builder()
                .login("jean")
                .password("collins@Pass@")
                .email("jean.collins@gmail.com")
                .build();
    

Please note that, Lombok @Builder behaves like @AllArgsConstructor. It won’t generate arguments for initialized final or static variables.

Use @Builder on a Method

Now that we know how to use the Lombok builder annotation on a class, let’s see how we can use it on the method level too.

If we have already defined a factory method to create our instances, then Lombok offers a simple way to make it as a builder.

We need just to declare our method with the @Builder annotation!

    
        public User(String login, String password, String email) {
            this.login    = login;
            this.password = password;
            this.email    = email;
        }

        @Builder(builderMethodName = "create")
        public static User createUser(String login, String password, String email) {
            return new User(login, password, email);
        }
    

@Builder(builderMethodName = “create”) tells Lombok to generate a method named create() that returns a Builder.

    
        public static User.UserBuilder create() {
            return new User.UserBuilder();
        }
    

Now, let’s create a new User object:

    
        User user = User.create()
            .login("alex")
            .password("alex@Pass")
            .email("alex.depp@hotmail.com")
            .build();
    

If we want to make our objects immutable, we can use @Value annotation.

Lombok Builder Constructor

We can use @builer annotation at the constructor level too. That way, we tell Lombok to create a builder for specific fields from the annotated constructor.

We can follow this approach, for example, when all fields are not required to create objects.

Let’s assume that we need only login and password to create User instances. Consider the following constructor:

    
        @Builder
        public User(String login, String password) {
            this.login = login;
            this.password = password;
        }
    

Now, let’s have a quick look at the Lombok generated code:

    
        public static class UserBuilder {
            private String login;
            private String password;

            UserBuilder() {
            }

            public User.UserBuilder login(final String login) {
                this.login = login;
                return this;
            }
            public User.UserBuilder password(final String password) {
                this.password = password;
                return this;
            }
            public User build() {
                return new User(this.login, this.password);
            }
            @Override
            public String toString() {
                return "User.UserBuilder(login=" + this.login + ", password=" + this.password + ")";
            }
        }
    

The generated builder class encapsulates only the login and the password fields, the ones that are already included in the constructor.

Builder Default Value

Lombok provides @Builder.Default to set default values for specific fields.

That way, the builder will use the defined value to initialize the annotated field in case we did not specify any value explicitly.

    
        @Getter
        @Builder
        public class User {

            @Builder.Default
            private String login = "admin";
            private String password;
        }
    

Let’s create a User instance without specifying the login attribute:

    
        public static void main(String[] args) {
            
            User user01 = User.builder().password("Pass").build();
            System.out.println(user01.getLogin());  //admin
            
        }
    

Nice! Lombok used the default value admin, which we implicitly declared using @Builder.Default.

Conclusion

In this quick tutorial, we have explained the Lombok Builder annotation. Along the way, we showcased, through practical examples, how to use @Builder to implement the builder pattern.

Happy reading, and stay tuned!