KapreSoft
Thank you for unblocking ads; your support allows us to continue delivering free, high-quality content that truly matters to you.

Lombok • Builders and Copy Constructors

 
 

Overview

Lombok’s builder and copy constructor feature using @Builder is a mechanism that allows the implementation of the Builder Pattern and Copy Constructors in Object-Oriented Programming. This article further illustrates how Lombok mitigates the disadvantages of creating builder methods and copy constructors making Lombok’s @Builder a good foundation for design and code maintainability.

Project

For simplicity and speed of development the maven project provided in this example used the spring initializr page https://start.spring.io to generate the initial source code.

POM Dependencies

The spring initializr page configured the pom parent element as shown below.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.6</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

The dependencies block will include lombok. The tested, recommended and beneficial version of lombok version to use is specified in the spring-boot-dependencies-<version>.pom file that is packaged with the spring boot library.

As an example, after initially building the project with maven, you will find the following file in your local maven .m2 cache directory.

Line #141 on the pom file in the link and figure below shows the specified version of lombok.

$HOME/.m2/repository/org/springframework/boot/spring-boot-dependencies/2.6.1/spring-boot-dependencies-2.6.1.pom

Figure 1.Lombok Version Configured in Spring Dependencies POM

Lombok Version in POM

Developers may override the lombok version as needed in the properties section of the local project pom.xml file

Properties Section

<properties>
    <!-- ...other properties-->
    <lombok.version>[TARGET-LOMBOK-VERSION]</lombok.version>
    <!-- Example
    <lombok.version>1.18.22</lombok.version>
    -->
</properties>

Builders

Builders are methods designed to aid developers in creating objects. This design is also referred to as the Builder Pattern are one of the original The Gang of Four (GoF) patterns referred to by the four authors ( Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) of the book Design Patterns: Elements of Reusable Object-Oriented Software (1994).

Benefits of Builders

Builders allow developers to “encapsulate” code for construction and representation and provides “control” over the code of the construction process.

Disadvantages

  1. A concrete builder must be created for each object creation.
  2. Builder classes should be mutable.
  3. May hamper or complicate dependency injection.

Lombok, in general, mitigates the above pain points by making it convenient for developers to handle boilerplate code. Eliminating boilerplate code in creating a builder method is an attractive option for implementing the Builder Pattern.

Copy Constructors

Copy constructors allow a new object to be created from an existing object of the same class, as a copy of an existing object. If this operation is beneficial for a particular business process is executing a few copies.

Copy constructors are newly instantiated objects that are independent of the original instance. An immutable object can be transformed to another object with new immutable field values and passed on to downstream services without affecting the original instance.

One caveat worth mentioning is that copy constructor are not inheritable by subclasses. However, lombok provides an experimental feature that allows this type of structure via the lombok annotation @SuperBuilder.

Mutable Classes

@Data
@SuperBuilder(toBuilder=true)
public class SuperUser extends User {
}

@Data
@SuperBuilder(toBuilder=true)
public class User {
}

Immutable Classes

For an immutable Parent/Child relationship, a minor change should suffice by unwrapping some @Value compounded annotations.

The most convenient way to create an immutable class in Lombok is to use the @Value annotation instead of @Data. The @Value annotation represents a compound of annotations for the code generation process of an immutable class. In this scenario, we want to avoid using @Value since this will mark the parent class User as final preventing class SuperUser from extending it.

To transform our mutable classes to immutable ones, the changes and annotations are as follows:

@Value
@EqualsAndHashCode(callSuper = true)
@SuperBuilder(toBuilder = true)
public class SuperUser extends User {

    @Singular
    List<String> permissions;
    
    @Override
    public boolean isSuperUser() {
        return true;
    }
    
}

@SuperBuilder(toBuilder = true)
@EqualsAndHashCode
@AllArgsConstructor
@ToString
public class User {

    @Getter
    String firstName;
    @Getter
    String lastName;
    @Getter
    String email;
    
    public boolean isSuperUser() {
        return false;
    }

}

Caveats for Both Builder and Copy Constructors

  1. Maintenance
    • In Lombok, since the code is generated and can be ignored during test coverage, the maintenance is no longer an issue. Item #4 below still applies.
  2. Bloated Objects
    • The developer is editing the non-bloated lombok version of the class but there is a danger of annotation bloating.
  3. Noise for Test Coverage
    • can be mitigated by hinting the build process to ignore these generated code in maven (or gradle). This is a valid option because we don’t want to “Test the Framework”
  4. Changes in code generation code potentially introduce bugs downstream
    • Mitigated by having appropriate unit tests on business critical methods

Example Use Cases

ETL Use Case

A valid use case for builder and copy constructors could be if a situation exists where a developer needs to create user objects with base properties avoiding code duplication. This pattern normally occurs on data migration or during an ETL process where coding is involved.

Note that the .builder() is the builder method and .toBuilder() is the copy constructor in this example. These method names are the default method names in Lombok and can be configured to have different method names.

User baseUser = User.builder()
                    .role("User")
                    .company("Acme Corporation").build();
User roadRunner = user.toBuilder()
                      .email("road.runner@gmail.com").build();
User wileECoyote = user.toBuilder()
                       .email("wile.e.coyote@gmail.com").build();

Deep Copies of Objects with Immutable fields for Updates

Incremental Update of Object fields

If we are dealing with a pure immutable object, we can retrieve the current state, create a new state and call a service for update. In example below, an HTTP request arrives and we essentially convert the incoming User request object to our domain User object using builders.

final String newEmail = request.getNewEmail();
final String newLastName = request.getNewLastName();
final User user = userService.getUserDetails();
final User updatedUser = user.toBuilder()
                             .email(newEmail)
                             .lastName(newLastName).build();
userService.updateUserDetails(updatedUser);

This can be further enhanced in spring framework by implementing converters, which we will not cover in this doc. For demonstration purpose an example implementation of a converter would look as follows.


import org.springframework.core.convert.converter.Converter;

@Component 
public class UserConverter implements Converter<UserRequest, User> {

    private final UserService userService;
    
    public UserConverter(UserService userService) {
        this.userService = userService;
    }

    User convert(UserRequest userRequest) {
        final String newEmail = request.getNewEmail();
        final String newLastName = request.getNewLastName();
        final User user = userService.getUserDetails();
        return user.toBuilder().email(newEmail)
                   .lastName(newLastName).build();
    }
}

Unit Tests

The following are snippets for Unit test methods for User and SuperUser classes. To automate our checks and guarantee business critical methods like isSuperUser() to continue to work as expected, we would write tests for these classes.

UserTest

@Test
void builder() {
    final User johnDoe = User.builder().firstName("John")
            .lastName("Doe")
            .build();
    assertThat(johnDoe.getFirstName()).as("FirstName")
            .isEqualTo("John");
    assertThat(johnDoe.getLastName()).as("LastName")
            .isEqualTo("Doe");
}

@Test
void builder_IsNotSuperUser() {
    final User johnDoe = User.builder().firstName("John")
            .build();
    assertThat(johnDoe.isSuperUser()).as("IsSuperUser")
            .isFalse();
}

Full User tests can be found in UserTest.

SuperUserTest

@Test
void builder_IsSuperUser() {
    User superUser = SuperUser.builder()
            .firstName("John").lastName("Doe")
            .permission("canUpdate").permission("canDelete")
            .build();
    assertThat(superUser.isSuperUser())
            .isTrue();
}

@Test
void copyConstructor_ShouldHaveEqualFieldValues() {
    SuperUser johnDoe = SuperUser.builder()
            .email("johndoe@gmail.com")
            .permission("canUpdate").permission("canDelete")
            .build();
    SuperUser johnDoeCopy = johnDoe.toBuilder().build();
    assertThat(johnDoe).hasToString(johnDoeCopy.toString());
}

Full SuperUser tests can be found in SuperUserTest.

Summary

For this article, the Lombok @Builder annotation on a method was illustrated to create a builder and copy constructor via the default builder() and toBuilder() methods on immutable objects. A few points are also mentioned how the caveats with implementing builder and copy constructors in plain java implementations may no longer apply when using Lombok.

The GitHub repository that this article references can be found in https://github.com/kapresoft/lombok/tree/1.0.0. Refer to the main documentation on the repository on how to build this project in your local environment.


Lombok • The Good, The Bad, and The Ugly
Within the Java development community, Lombok often emerges as a polarizing subject. This library’s chief aim is to minimize the tedium of boilerplate code—a persistent thorn in the side of many Java developers. Nevertheless, every tool brings its own concessions to the table.
Lombok • @Value Annotation Best Practices
When it comes to clean coding and enhanced testability in Java applications, Project Lombok is a game-changer. Its @Value annotation not only simplifies your code but also enhances its readability, maintainability, and testability.
Lombok • @Data Annotation Best Practices
When it comes to clean and efficient Java coding, the power of Project Lombok cannot be overstated. Specifically, the @Data annotation provided by Lombok stands out as a valuable tool for Java developers.
Cleaner Code and Enhanced Testability: Harnessing the Power of Lombok Builders
In the realm of Java development, the quest for cleaner code and improved testability is ever-present. One formidable ally in this quest is Project Lombok, a mature library that revolutionizes the way Java developers handle boilerplate code.
Lombok val vs var
Lombok has gained immense popularity among Java developers for its ability to simplify coding practices by reducing boilerplate code. In the vast ocean of features offered by Lombok, two features stand out: val and var. In this deep dive, we’ll uncover the secrets of these two variables and demonstrate their utility.
Lombok Test Coverage
When it comes to software development, testing plays a crucial role in ensuring the quality and reliability of the codebase. Test coverage, in particular, is a metric that measures the extent to which the source code of a program has been tested.
Lombok Disadvantages
In the world of Java development, optimizing code efficiency and reducing boilerplate is a constant pursuit. To address these challenges, various tools and libraries have emerged, and one such popular option is Lombok—a Java library that offers annotations for code generation, resulting in benefits such as time-saving and code simplification. However, as with any tool, there are both advantages and drawbacks to consider.
Lombok @Singular Annotation
Lombok is a Java library that provides a set of annotations and utility classes to reduce boilerplate code in Java projects. One of its popular annotations is @Singular, which generates builder methods for collections.
Java • Avoid Getters & Setters Methods With Lombok
This article provides an overview of how to avoid the repetitive code associated with writing getter and setter methods in Java classes using Lombok. By using Lombok’s annotations, such as @Getter and @Setter, developers can generate these methods automatically, thereby reducing the amount of boilerplate code required in their classes.
Lombok • @AllArgsConstruct vs @RequiredArgsConstructor
What is the difference between @AllArgsConstruct and @RequiredArgsConstructor in Lombok? The main difference between @AllArgsConstructor and @RequiredArgsConstructor is in the fields that are included in the generated constructor.
Lombok • How to Use Constructor
To use the constructor with Lombok, you can annotate your class with @AllArgsConstructor or @RequiredArgsConstructor. @AllArgsConstructor generates a constructor that takes in all the fields in the class as arguments, while @RequiredArgsConstructor generates a constructor that takes in only the final or @NonNull fields as arguments.
Lombok • An Overview
Lombok is a Java library that provides annotations to help reduce boilerplate code and increase developer productivity. By using Lombok annotations, Java developers can automatically generate common code such as getters, setters, constructors, equals and hashCode methods, and more.
Lombok • Exclude Generated Code From Test Coverage
When using Lombok in a Java project, the library generates code at compile-time, such as getters, setters, constructors, equals and hashCode methods, and more.
Lombok • Using @With Annotation to Clone Immutable Objects
Using Lombok’s @With Annotation to Clone Immutable Objects is a beneficial feature that helps developers minimize code replication and ceremonial code. It is the next best alternative to Copy Constructs in object-oriented programming. The @With annotation also requires @AllArgsConstructor to function correctly.