Let's get started with a Microservice Architecture with Spring Cloud:
Using Lombok’s @Builder Annotation
Last updated: July 25, 2024
1. Overview
Project Lombok’s @Builder is a helpful mechanism for using the Builder pattern without writing boilerplate code. We can apply this annotation to a Class or a method.
In this quick tutorial, we’ll look at the different use cases for @Builder.
2. Maven Dependencies
First, we need to add Project Lombok to our pom.xml:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
Maven Central has the latest version of Project Lombok here.
3. Using @Builder on a Class
In the first use case, we’re merely implementing a Class, and we want to use a builder to create instances of our class.
The first and only step is to add the annotation to the class declaration:
@Getter
@Builder
public class Widget {
private final String name;
private final int id;
}
Lombok does all the work for us. We can now build a Widget and test it:
Widget testWidget = Widget.builder()
.name("foo")
.id(1)
.build();
assertThat(testWidget.getName())
.isEqualTo("foo");
assertThat(testWidget.getId())
.isEqualTo(1);
If we want to create copies or near-copies of objects, we can add the property toBuilder = true to the @Builder annotation:
@Builder(toBuilder = true)
public class Widget {
//...
}
This tells Lombok to add the toBuilder() method to our Class. When we invoke the toBuilder() method, it returns a builder initialized with the properties of the instance it’s called on:
Widget testWidget = Widget.builder()
.name("foo")
.id(1)
.build();
Widget.WidgetBuilder widgetBuilder = testWidget.toBuilder();
Widget newWidget = widgetBuilder.id(2).build();
assertThat(newWidget.getName())
.isEqualTo("foo");
assertThat(newWidget.getId())
.isEqualTo(2);
We can see in the test code that the builder class generated by Lombok is named like our class with “Builder” appended to it, WidgetBuilder in this case. We can then modify the properties we want, and build() a new instance.
If we need to specify the required fields, we can use the annotation configuration to create an auxiliary builder:
@Builder(builderMethodName = "internalBuilder")
public class RequiredFieldAnnotation {
@NonNull
private String name;
private String description;
public static RequiredFieldAnnotationBuilder builder(String name) {
return internalBuilder().name(name);
}
}
In this case, we’re hiding the default builder as internalBuilder and creating our own. Thus, when we create the builder, we must provide the required parameter:
RequiredField.builder("NameField").description("Field Description").build();
Also, to make sure our field exists, we can add the @NonNull annotation.
4. Using @Builder on a Method
Suppose we’re using an object that we want to construct with a builder, but we can’t modify the source or extend the Class.
First, let’s create a quick example using Lombok’s @Value annotation:
@Value
final class ImmutableClient {
private int id;
private String name;
}
Now we have a final Class with two immutable members, getters for them, and an all-arguments constructor.
We covered how to use @Builder on a Class, but we can also use it on methods. We’ll use this ability to work around not being able to modify or extend ImmutableClient.
Then we’ll create a new class with a method for creating ImmutableClients:
class ClientBuilder {
@Builder(builderMethodName = "builder")
public static ImmutableClient newClient(int id, String name) {
return new ImmutableClient(id, name);
}
}
This annotation creates a method named builder() that returns a Builder for creating ImmutableClients.
Now let’s build an ImmutableClient:
ImmutableClient testImmutableClient = ClientBuilder.builder()
.name("foo")
.id(1)
.build();
assertThat(testImmutableClient.getName())
.isEqualTo("foo");
assertThat(testImmutableClient.getId())
.isEqualTo(1);
5. Exclude Fields From Builder
Typically, excluding fields from a builder refers to omitting some attributes when constructing objects. This can be very handy when we want to create an object with a specific configuration or when certain fields are irrelevant or optional.
In short, we can use the @Builder annotation to mark directly a custom factory method or a constructor that excludes particular fields.
So, let’s see it in action. For instance, we’ll consider the ClassWithExcludedFields class:
public class ClassWithExcludedFields {
private int id;
private String includedField;
private String excludedField;
}
Next, let’s add a static method that excludes excludedField:
@Builder(builderMethodName = "customBuilder")
public static ClassWithExcludedFields of(int id, String includedField) {
ClassWithExcludedFields myObject = new ClassWithExcludedFields();
myObject.setId(id);
myObject.setIncludedField(includedField);
return myObject;
}
Here, excludedField isn’t part of the method parameters. So, customBuilder won’t generate a method to set a value for it.
Finally, let’s create a test case to confirm that everything works as expected:
@Test
public void whenUsingCustomBuilder_thenExcludeUnspecifiedFields() {
ClassWithExcludedFields myObject = ClassWithExcludedFields.customBuilder()
.id(3)
.includedField("Included Field")
// .excludedField() no method to set excludedField
.build();
assertThat(myObject.getId()).isEqualTo(3);
assertThat(myObject.getIncludedField()).isEqualTo("Included Field");
}
As we see above, customBuilder allows customizing which fields to include or exclude in the builder.
Since Lombok v1.16.16, we can alternatively use the @Builder.Default annotation to set a default value for the field we want to ignore in the builder. For example, let’s annotate excludedField with @Builder.Default:
@Builder.Default
private String excludedField = "Excluded Field using Default";
It’s straightforward and readable. The specified default value is used when the field isn’t explicitly set in the builder.
Lastly, let’s confirm this using another test case:
@Test
public void whenUsingBuilderDefaultAnnotation_thenExcludeField() {
ClassWithExcludedFields myObject = ClassWithExcludedFields.builder()
.id(3)
.includedField("Included Field")
.build();
assertThat(myObject.getId()).isEqualTo(3);
assertThat(myObject.getIncludedField()).isEqualTo("Included Field");
assertThat(myObject.getExcludedField()).isEqualTo("Excluded Field using Default");
}
As we can see, excludedField is set to the default value even though we didn’t use it in the builder.
6. Conclusion
In this brief article, we used Lombok’s @Builder annotation on a method to create a builder for a final Class, and we learned how to make some of the Class fields required.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
















