Heart containing Coding Chica Java 101

Boil Away The Boilerplate! Adding Lombok Code Generation

TIP: References Quick List

Table of Contents

  1. Table of Contents
  2. Introduction
  3. Update the Build
  4. Maven Build – Success
  5. Unit Test Updates
    1. Why Unit Test Generated Code?
  6. Runtime Code Updates
  7. Maven Build – Success
  8. Caution
  9. Commit

Introduction

You may have guessed from the last post that getter and setter logic is very boilerplate (AKA standardized). As long as we intend to follow standardized rules, we can hide those uninteresting, boilerplate details in the source code by using the Lombok library. This library contains Java annotations that can be used in your code to indicate, when the code is compiled, that standardized methods/logic/behaviors should be added to the compiled code.

Let’s add this to our build and source code.

Update the Build

Let’s add Lombok to the Maven build first. In the pom.xml, add the following to the dependencies section:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.28</version>
    <scope>provided</scope>
</dependency>

Add Lombok as a dependency. The scope: provided means that we do not want to include Lombok in the resulting artifact(s) and that when we use Lombok we will tell the code where to find it on the file system. In this case, we only want the changes that Lombok will make during the compilation process.

Next, let’s update the existing configuration for the maven-compiler-plugin to have it look for the Lombok annotations in our code when compiling it:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.28</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

This approach not only tells Lombok that we want to include Lombok in our compile-time logic, but also where to find it on our local machine. When you run a Maven build, it downloads plugins and dependencies from a central repository and stores a cache of them locally (a copy for quick use)…often within a nested folder structure inside of: ${user.home}/.m2/repository, where ${user.home} contains the current user’s home directory.

However, you may have noticed that we now have the Lombok version specified in both places. We don’t want to have to keep the two values in sync. Instead, let’s create a new entry in the properties section of the pom.xml:

<lombok.version>1.18.28</lombok.version>

Now, we can change both of the version values in our prior updates to instead reference this new property. This way we will only have one place to update in the future:

<version>${lombok.version}</version>

Maven Build – Success

If we run the Maven build now, it should be successful. However, no actual changes have been made to the source code of our application. Still, it is good to test this as an incremental step, before moving on to the application’s code changes.

Unit Test Updates

We have none! This is one of the benefits of taking the time to setup automated tests in our last post. The unit tests are already in place, all we need to do is re-run the Maven build to execute them after our runtime changes.

Why Unit Test Generated Code?

Some will argue that it is not worthwhile to setup or keep unit tests for generated code, as Lombok performs unit testing on their code and it should be generated, as expected. However:

  • Generated code like Lombok getters / setters are based upon the presence and configuration of annotations (part of your source code).
    • Did those annotations get added correctly?
    • Did we setup the the configurations necessary to produce the generated code with the desired tweaks?
  • What would happen if your team / organization changed direction and no longer wanted generated code, such as with Lombok?
    • As the team or organization evolves over time, opinions about whether or not Lombok is acceptable to use may evolve and change. Having automated unit tests ready to validate the code expectations after compilation may save the team significant time and avoid other project delays, if such a direction change mandate occurs in the future.
    • Lombok provides a delombok feature, that allows you to see current source code if Lombok was removed from the compiler (with the modifications). However, that will not include any unit tests.
  • What if you need to upgrade the version of Lombok in use and there may be changes to the annotations or configurations supported?
  • What if you decide to move the annotations around in your source code – from fields to the class / vice versa. Did you make all of the expected updates?
  • What if you change from one Lombok annotation set (such as @Getter and @Setter) to another, such as @Data or @Value or some other annotation.
    • Did you correctly understand what changes those new annotations cause in the source code?
    • Will a novice / more junior developer assigned to update your code have the correct/same understanding?
  • What if we have private fields that we do not want exposed to calling code as getters and setters? Were someone to move the @Getter and @Setter annotations from the specific fields to instead reside at the class level, then we may accidentally expose this internal-only data. Adding a unit test to ensure that no getter/setter methods exist would catch such issues at build-time, rather than possibly introducing a change that requires a breaking change (which sometimes requires a year or more of notice to customers about the deprecation / plan to remove the methods) to fix.

Without unit tests, we would either be less confident in our changes, or the migration process would take longer. Therefore, I would argue the unit tests for the Lombok-generated code is still a beneficial and necessary part of our unit test suite.

Runtime Code Updates

We can now remove the getName and setName methods from our codingChica.java101.App class. We can instead replace them with @Getter and @Setter or similar annotations to have Lombok generate them at compile-time. For this example, let’s add them to the name field:

/**
 * The name of the user of the application.
 */
@Getter
@Setter
private String name;

Maven Build – Success

Even though we removed our getName and setName methods, our unit tests and build should still succeed because of the new annotations and the Lombok generated code during the compilation.

Caution

Think carefully when adding Lombok annotations.

  • Depending upon which annotations you chose, adding them may increase the learning curve for newcomers to the team.
    • The @Data annotation combines @Getter, @Setter, @ToString, @EqualsAndHashCode and @RequiredArgsConstructor annotations. For a new engineer on your team, who is not familiar with Lombok annotations, they may not realize all of the behavior changes that occur with this one annotation.
    • The @Value annotation has similar concerns, as it is shorthand for: final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter.
  • Overriding toString, equals and hashCode may introduce performance impacts and/or runtime exceptions when using an Object Relational Mapping (ORM) database, such as Hibernate, as described here.
  • Certain annotations may be difficult to fully test (achieve 100% code coverage) in the generated code. For example, @EqualsAndHashCode is difficult to validate all of the corner cases. If needed, I have found that an alternative library, such as Apache Common’s options may better-suit my needs:

Commit

As we want to commit in small, incremental changes, now is a good time.

Boil Away The Boilerplate! Adding Lombok Code Generation

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.