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.