To write efficient and maintainable code, you need to focus on simplicity and readability.
In Java, object creation is a fundamental concept that can greatly impact the overall quality of your code. When you create objects in a complex way, it can lead to a code which is hard to understand and modify.
In this tutorial, you’ll learn how to use @Builder annotation in lombok to ease your object creation process.

Understanding Lombok and Object Creation

Lombok @Builder annotation allows you to create objects in a more concise and expressive way, making your code more readable and maintainable.

When creating objects in Java, you often need to provide multiple constructors, each with different parameters, to accommodate various use cases.
This can lead to a constructor explosion, making your code harder to read and understand.
Additionally, when working with complex objects, you may need to create multiple objects and set their properties individually, which can be error-prone and time-consuming.

By simplifying object creation, you can reduce the amount of code you need to write, making your development process more efficient.
With Lombok’s @Builder annotation, you can create objects in a more declarative way, focusing on the properties of the object rather than the process of creating it.

@Builder annotation is a Lombok feature that allows you to generate a builder class for your Java objects.
Its primary purpose is to provide a fluent API for creating objects, making it easier to construct complex objects in a readable and maintainable way.
By using @Builder, you can avoid the tedious process of creating multiple constructors or setter methods, and instead, focus on writing clean and concise code.

Lombok @Builder Annotation Example

For example, consider the following simple class

public class Person { 
  private String firstName; 
  private String lastName; 
  private int age; 

  public Person(String firstName, String lastName, int age) { 
    this.firstName = firstName; 
    this.lastName = lastName; 
    this.age = age; 
  } 

  public String getFirstName() { 
    return firstName; 
  } 

  public void setFirstName(String firstName) { 
    this.firstName = firstName; 
  } 

  public String getLastName() { 
    return lastName; 
  } 

  public void setLastName(String lastName) { 
    this.lastName = lastName; 
  } 

  public int getAge() { 
    return age; 
  } 

  public void setAge(int age) { 
    this.age = age; 
  } 
}

Without @Builder, you would typically create a User object using a constructor or setter methods as below

User user = new User(); 
user.setFirstName("John"); 
user.setLastName("Doe"); 
user.setAge(30); 
user.setEmail("johndoe@example.com");

As you can see, creating a simple User object requires a lot of code.
And this is just a basic example – imagine dealing with more complex objects that have multiple dependencies and relationships.

With Lombok’s @Builder annotation, you can simplify this class and reduce the amount of boilerplate code

@Builder 
public class Person { 
  private String firstName; 
  private String lastName; 
  private int age; 
}

By annotating the User class with @Builder, Lombok generates a builder class that allows you to create objects in a more fluent and expressive way.
Now you can create a User object as

User user = User.
            builder().
            firstName("John").
            lastName("Doe").
            age(30).
            email("johndoe@example.com").
            build();

As you can see, the @Builder annotation simplifies the object creation process, making your code more expressive and easier to maintain.

Placement of @Builder Annotation

@Builder annotation can be placed on a class, constructor, or method to enhance flexibility.
This enables you to select the optimal approach for your particular use case, whether you need to generate a builder for a class, constructor, or method.

A. Constructor Mode

When you apply @Builder to a constructor, it is treated as a static method that returns the class being constructed, and its type parameters are the same as the type parameters of the class itself.

This mode is useful when you want to generate a builder for a specific constructor.
You can apply @Builder to a constructor, and it will generate a builder class with the same type arguments as the constructor.

B. Class Mode

Builder annotation on a class is similar to applying @AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and annotating the all-args-constructor with @Builder.
This mode is useful when you want to generate a builder for the entire class.

When you apply @Builder to a class, it generates a builder class with the same type arguments as the class.

C. Method Mode

Works similarly to the constructor mode, but with a method annotated with @Builder.
The method is called the target, and it generates an inner static class named FooBuilder, with the same type arguments as the static method.

@Builder Vs @Data and @Value

Other Lombok annotations like @Data or @Value also simplify the process of creating objects in Java.
However, @Builder annotation serves a distinct purpose and offers unique benefits compared to these other annotations.

For instance, @Data annotation is used to generate getters, setters, and other boilerplate code for a class.
It’s a convenient way to create a simple data carrier object, but it doesn’t provide the same level of flexibility and customization as the @Builder annotation.
With @Data, you’re limited to the default getter and setter methods generated by Lombok, whereas @Builder allows you to define custom builder methods and control the object creation process more precisely.

On the other hand, @Value annotation is used to create immutable objects, which can be useful in certain scenarios.
However, it doesn’t provide the same level of flexibility as @Builder when it comes to creating objects with complex dependencies or nested objects.
With @Value, you’re limited to creating objects with a fixed set of properties, whereas @Builder allows you to create objects with dynamic properties and complex relationships.

To illustrate the difference, consider the following example

@Data
public class User { 
  private String firstName; 
  private String lastName; 
  private int age; 
}

In this example, @Data annotation generates default getter and setter methods for the User class.
However, if you want to create a User object with a custom builder method, you would need to use the @Builder annotation instead

@Builder
public class User { 
  private String firstName; 
  private String lastName; 
  private int age; 

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

  public static class UserBuilder { 
    private String firstName; 
    private String lastName; 
    private int age; 
    public UserBuilder withFirstName(String firstName) { 
      this.firstName = firstName; 
      return this; 
    } 

    public UserBuilder withLastName(String lastName) { 
      this.lastName = lastName; return this; 
    } 

    public User build() { 
      return new User(firstName, lastName, age); 
    } 
  } 
}

In this example, @Builder annotation allows you to define a custom builder method, withFirstName(), which provides a more flexible and intuitive way of creating User objects.
This level of customization is not possible with the @Data or @Value annotations.

Configurable Aspects of Builder

@Builder provides a range of configurable aspects that allow you to customize the generated builder API to suit your needs.

A. Builder Class Name

The name of the builder class can be configured.
By default, it is title-cased form of the return type of the target, suffixed with “Builder”.
For example, if @Builder is applied to a class named com.codippa.FancyList, then the builder name will be FancyListBuilder.

But you can change this default to your own class name as below

@Builder(builderClassName="AppUserBuilder)
public class User { 
  private String firstName; 
  private String lastName; 
  private int age; 

  public static class AppUserBuilder { 
    private String firstName; 
    private String lastName; 
    private int age; 
    // other methods
  } 
}

B. Build Method Name

With @Builder, you can also configure the name of the build() method. By default, it is simply “build”.

build() method is responsible for calling the target method, passing in each field, and returning the same type that the target returns.

You can do this by using buildMethodName attribute as

@Builder(buildMethodName="createUser")
public class User { 
  // other fields and methods
}

And then you need to call this method to create an object.

User user = User.
            builder().
            firstName("John").
            lastName("Doe").
            age(30).
            email("johndoe@example.com").
            createUser();

C. Builder Method Name

You can even change the name of the builder() method. By default, it is simply “builder” as in the previous examples.

To change it, use builerMethodName attribute as below

@Builder(builderMethodName="userBuilder")
public class User { 
  // other fields and methods
}

Now you need to change the user builder method name as below

User user = User.
            userBuilder().
            firstName("John").
            lastName("Doe").
            age(30).
            email("johndoe@example.com").
            build();

D. Setter Methods

By default, the setter methods are named with the same name as the properties or instance variables of the class.

@Builder annotation also allows you to customize the names of setter methods with setterPrefix option as shown below

@Builder(setterPrefix="init")
public class User { 
  String email;
}

Now you can set the values of properties as

User.
builder().
initEmail("abc@xyz.com")

Setting Default Values

There are cases where the fields of a class are not explicitly set. They are then initialized to the default values according to their data types such as int to 0, boolean to false, objects to null and so on.

Lombok’s @Builder annotation provides an option to set default values for fields if you do not want to set them using setter methods using its Default interface as shown below

@Builder
public class User { 
  String email;
  @Builder.Default boolean isAdmin = false;
}

Handling Nested Objects

Nested objects means when a class contains fields which are of types of other class.
Unlike simple objects, nested objects can be a challenge to create and manage, especially when using traditional Java constructors or setters.
@Builder annotation in Lombok provides a convenient way to handle nested objects, making your code more concise and easier to read.

Let’s consider an example where you have a User object that contains a nested Address object.
Without Lombok, you would typically create a User object like this

User user = new User(); 
user.setName("John Doe"); 
user.setAge(30); 
Address address = new Address(); 
address.setStreet("123 Main St"); 
address.setCity("Anytown"); 
address.setState("CA"); 
address.setZip("12345"); 
user.setAddress(address);

As you can see, creating a User object with a nested Address object requires a lot of boilerplate code.
With Lombok’s @Builder annotation, you can simplify this process significantly

@Builder 
public class User { 
  private String name; 
  private int age; 
  @Builder.Default 
  private Address address = Address.builder().build(); 
} 
@Builder 
public class Address { 
  private String street; 
  private String city; 
  private String state; 
  private String zip; 
}

Now, you can create a User object with a nested Address object in a single line of code

User user = User.
            builder().
            name("John Doe").
            age(30).
            address(Address.
                    builder().
                    street("123 Main St").
                    city("Anytown").
                    state("CA").
                    zip("12345").
                    build()).
            build();

As you can see, using the @Builder annotation makes it easy to create nested objects in a concise and readable way.
This not only reduces the amount of code you need to write but also makes your code more expressive and easier to maintain.

Conclusion

In this article, we learnt about @Builder annotation in Lombok and its power in simplifying object creation in Java.
With this annotation, you can significantly reduce boilerplate code, improve code readability, and simplify object creation and modification.