This article discusses the use of Lombok's @Builder annotation in Java and its advantages and drawbacks, suggesting an alternative approach using Lombok's fluent setters for a more straightforward implementation.
Abstract
Lombok is a Java library that generates boilerplate code, and its @Builder annotation implements the builder design pattern. However, configuring Lombok for specific scenarios can be complex due to its "invisible magic." The article demonstrates the difficulty of implementing a mandatory field and calculating the value of a field based on another mandatory field using @Builder. It then introduces Lombok's fluent setters as a more straightforward alternative, allowing chaining of setters and providing a cleaner, easier-to-understand implementation.
Bullet points
Lombok is a Java library that generates boilerplate code, including the builder design pattern using the @Builder annotation.
Configuring Lombok for specific scenarios can be complex and hard to understand.
Implementing a mandatory field and calculating the value of a field based on another mandatory field using @Builder is difficult.
Lombok's fluent setters provide a more straightforward alternative, allowing chaining of setters and a cleaner implementation.
Fluent setters return the instance itself after assigning a value, enabling the creation of a Student instance by providing the mandatory field for the constructor and then chaining other attributes.
The implementation using fluent setters is cleaner and easier to understand than using @Builder.
Lombok should be used responsibly, keeping implementations simple and easy to understand.
You Might Stop Using Lombok’s @Builder After Reading This
Lombok is a Java library that can generate known patterns of code for us, allowing us to reduce the boilerplate code. For instance, we can leverage Lombok to generate all the getters and setters for a class.
In this article, we’ll discuss Lombok’s @Builder annotation, which implements the builder design pattern for us. We’ll see its advantages and some of the drawbacks it can bring.
After that, we’ll use Lombok’s configuration to achieve the same functionality in a more straightforward manner.
2. @Builder and Lombok’s Magic
Let’s start by looking into a simple use-case where the @Builder annotation can come in handy.
For the code examples in this article, we’ll use the Student data model:
Because our class is annotated with @Builder, we can instance a Student using syntax associated with the builder design pattern:
This feature is great for simple data classes with many fields. Though, if we need something more specific, things can get tricky really fast.
One of Lombok’s biggest complaints is the “invisible magic” it performs. In order to configure it for more specific scenarios, we first need to understand how Lombok works and what it generates.
3. Having a Mandatory Field
Let’s assume the firstName field is mandatory when instantiating a Student. This is a very simple and reasonable scenario.
One of the most recommended solutions to achieve this with Lombok is to:
annotate the field with lombok.NonNull
provide a different name for the method returning Lombok’s builder
manually implement the builder() method and invoke Lombok’s builder by passing the firstName
As a result, we can create the Student builder only if we provide the firstName:
We haven’t added a lot of extra code. Nonetheless, the result is a bit ambiguous and hard to understand, especially if you are not very familiar with Lombok.
4. Calculate The Value of a Field Based On Another Mandatory Field
For this example, let’s assume we want to manually generate a studentId based on the mandatory firstName field.
Even though the requirement is not complex, the suggested implementation is very hard to understand.
we’ll need a private constructor with all the fields, except the one that is calculated.
we’ll annotate set all the fields here and calculate the studentId
then, we’ll annotate this with @Builder and give it a custom name, following the steps from section 3 (to ensure firstName is NonNull)
We can think of other scenarios, such as adding inheritance, default values... But I believe this is already too much. We’ve got the point, we need a different solution.
We can configure Lombok to allow chaining the setters. This simply means the setters will return the instance itself after assigning a value.
This will allow us to build a Student instance using this syntax:
Student student = new Student().setFirstName("John").setYear(2);
Furthermore, Lombok allows us to rename the setters, to get a more fluent API for building the object:
Student student = new Student().firstName("John").lastName("Doe");
We can configure these properties at a class level, using the @Accessors annotation. Let’s use this approach for the scenario described in the previous section:
We are now exposing the fluent setters. As a result, we can now create a Student instance by providing the mandatory field for the constructor and then chain the other attributes:
Student student = new Student("John").lastName("Doe").year(2);
6. Conclusion
If we compare the two implementations, we can all agree that the one with fluent setters is much cleaner and easier to understand.
This is mostly because we are using Lombok to generate some simple setters instead of using it to implement the builder design pattern.
Consequently, it’s easier to visualize the code that is being generated and understand what Lombok is doing.