Lombok: Unhinge Yourself From Boilerplate Code

Unpackaged Reviews
Geek Culture
Published in
7 min readJan 28, 2021

--

Overview

Java is a strongly-typed, object-oriented programming language, which often translates to strict request, response, and data-transfer objects. It provides the developer with a clear picture of the data available for consumption in any class, but adds a lot of boilerplate code such as getters, setters, and let us not forget the constructor. Not only is it painful to write and maintain these methods, but they also add clutter to the code. Project Lombok allows us to write clutter-free code with impressive community support.

Highlights

Lombok is a code generator and kicks in during compile time. Before diving deep into the features of the package, let us see how to include them in our configuration files:

/* Maven */<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
/* Include in build section of the pom file */<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</path>
</annotationProcessorPaths>
/* Gradle */dependencies {
compileOnly 'org.projectlombok:lombok:$version'
annotationProcessor 'org.projectlombok:lombok:$version'

testCompileOnly 'org.projectlombok:lombok:$version'
testAnnotationProcessor 'org.projectlombok:lombok:$version'
}

If you are using IntelliJ IDEA, it is advisable to install the Lombok plugin for a better developer experience.

Encapsulation simplified

  • Getters and Setters:
    Getters and Setters are an important aspect of encapsulation, they allow the class to decide what data is available to other objects. In contrast, writing Getters and Setters for large classes add to the clutter. Lombok provides the @Getter and @Setter annotation which can be placed at the class or field level. If you want all fields of the class to have the Getters/Setters, then it is advisable to add the annotation to the whole class. Let us look at a standard Student class:
public class Student {

private String name;
private String identityNumber;
private Integer grade;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getIdentityNumber() {
return identityNumber;
}

public void setIdentityNumber(String identityNumber) {
this.identityNumber = identityNumber;
}

public Integer getGrade() {
return grade;
}

public void setGrade(Integer grade) {
this.grade = grade;
}

public Student(String name, String identityNumber, Integer grade) {
this.name = name;
this.identityNumber = identityNumber;
this.grade = grade;
}
}

Now let us add some Lombok magic:

@Getter
@Setter
public class Student {

private String name;
private String identityNumber;
private Integer grade;

public Student(String name, String identityNumber, Integer grade) {
this.name = name;
this.identityNumber = identityNumber;
this.grade = grade;
}
}
  • Constructors:
    Lombok provides three different annotations to generate constructors for your classes. @AllArgsConstructor adds a constructor for all the fields in the class. Correspondingly, @NoArgsConstructor adds an empty constructor. But what if you want a constructor only for the non-final fields in your class, @RequiredArgsConstructor achieves precisely this.
  • hashcode, equals and toString methods:
    Overriding the toString() and equals() methods are some of the most predictable boilerplate snippets. Interestingly, it can be daunting to standardize them across the codebase. Lombok has provided the @ToString and @EqualsAndHashCode annotations that auto-generate the methods whenever we compile. Both annotations are placed at the class level and allow the developer to include fields from the superclass by passing the attribute callSuper as true.

So you can simplify a Plain Old Java Object of Student type as shown below

@Getter
@Setter
@AllArgsConstructor
@EqualsAndHashCode
@ToString
public class Student {

private String name;
private String identityNumber;
private Integer grade;
}

However, if you are finding it cumbersome to add @AllArgsConstructor, @Getter, @Setter and @EqualsAndHashCode to every class. You can replace it with @Data, a simple but powerful alias.

Immutability enhanced

Let us first write an immutable class.

public final class Student {

private final String name;
private final String identityNumber;
private final Integer grade;

public String getName() {
return name;
}

public String getIdentityNumber() {
return identityNumber;
}

public Integer getGrade() {
return grade;
}

public Student(String name, String identityNumber, Integer grade) {
this.name = name;
this.identityNumber = identityNumber;
this.grade = grade;
}
}

We can use the @Value annotation to mark every field in the class as final. It also adds a equals() and hashCode method to the class. Hence, It can be considered as the immutable variant of @Data. We can also make a field non-final by using @NonFinal on top of the field. But it would not be completely immutable though. Our Student class now looks like this:

@Value
public class Student {

private final String name;
private final String identityNumber;
private final Integer grade;
}

Do not build builders anymore

The builder pattern is a clean way of building complex objects. Unfortunately, writing builders for every complex or large class feels laborious. Lombok provides the @Builder annotation with an optional toBuilder attribute. If the attribute is set to true, then a method to obtain the builder from the built object will be provided.

/* without @Builder */public class Student {

private final String name;
private final String identityNumber;
private final Integer grade;

public static Builder builder(){
return new Builder();
}

public static class Builder {
private String name;
private String identityNumber;
private Integer grade;

public Builder name(String name){
this.name = name;
return this;
}

public Builder identityNumber(String identityNumber){
this.identityNumber = identityNumber;
return this;
}

public Builder grade(Integer grade){
this.grade = grade;
return this;
}

public Student build(){
return new Student(name, identityNumber, grade);
}
}
}
/* with @Builder */@Builder
public class Student {

private final String name;
private final String identityNumber;
private final Integer grade;
}

we can consume the builder class as shown below:

Student student = Student.builder()
.name("unpackaged")
.identityNumber("1A045")
.grade(9)
.build();

Write synchronous code easily

When writing a multi-threaded application, most critical sections are marked with a synchronized block. A sample code snippet is shown below:

public class Student {

private String name;
private String identityNumber;
private Integer grade;
private final Object lock = new Object();

public Integer updateGrade(Integer marks){
synchronized (lock) {
if (marks > 40) {
grade++;
}
}
return grade;
}
}

Managing multiple such lock variables adds to the clutter and may lead to some code smells. We can offload such blocks to the @Synchronized annotation of Lombok. It adds a $LOCK variable to the static and non-static scope of the class if not present and wraps the critical section inside a synchronized block. It allows developers to concentrate only on the business logic. You can also pass a string that would be taken as the lock variable name. The above class would be transformed as shown below:

public class Student {

private String name;
private String identityNumber;
private Integer grade;
private final Object lock = new Object();

@Synchronized
public Integer updateGrade(Integer marks){
if (marks > 40) {
grade++;
}
return grade;
}

@Synchronized("lock")
public String getName(){
return name;
}
}

Experimental features

Apart from the above annotations, there are many more code generators available. Lombok follows the canary approach. Initially, a code generator is available in the experimental package. After observing its adoption and bug fixes it is promoted to the main package. Let us look at some interesting experimental annotations in brief:

  • @FieldNameConstants : Creates an inner class with constants for all the fields in the class upon which the annotation exists. It is interesting to note that it does not generate a constant for the methods or one for the class name. It would be better if they could add this to the annotation before marking it stable for use.
  • @UtilityClass : Adds a private constructor to the annotated class and marks all fields and methods in the class as static.
  • @Slf4j : Adds a logger object to the class with the configured logger.

Although these are not as powerful as some of the other generators, they still make the code predictable and less prone to human error such as creating an object of a utility class or configuring the wrong logger for a class!

I don't want to use Lombok anymore!

If at any point in time you want to remove Lombok from your application, you can use the Delombok tool to generate the boilerplate code and remove the application from the build configuration file. Delombok is available within the Lombok jar file. Developers can run the below command to Delombok their application:

java -jar lombok.jar delombok <source-direcory> -d <destination-directory>

IDEA also provides an option to Delombok selected files. You can also use the maven plugin to achieve the same.

Evaluation Metrics

Conclusion

We cannot stress enough how much time Lombok has saved while removing clutter that was once common in the daily discourse of a Java developer. Unlike standard annotation processors, Lombok uses internal Java APIs and injects the code into the class. It has hence raised concerns of developers on its stability as Java adopts a faster release cycle. However, if you consider all the advantages and the fact that you can Delombok your code anytime, we can embrace it without concerns. It makes Java code look cool again.

As a community, they are extremely active. Anyone can join their email thread and start a thread on a possible feature or enhancement. They are also cautious on what they deem to be stable, hence, putting any doubt to sleep.

Check out the package and some reading materials

Disclosures

The content and evaluation scores mentioned in this article/review is subjective and is the personal opinion of authors at Unpackaged Reviews based on everyday usage and research on popular developer forums. They do not represent any company’s views and are not impacted by any sponsorships/collaboration.

--

--

Unpackaged Reviews
Geek Culture

We’re a small team of devs reviewing code packages to help choose the one for your next project!