You've successfully subscribed to WorldRemit Technology Blog
Great! Next, complete checkout for full access to WorldRemit Technology Blog
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.
Billing info update failed.
Deep into Lombok

Deep into Lombok

. 4 min read

| Image by Tiket2 Fly via Unsplash Copyright-free

Project Lombok is a java library that automatically plugs into your editor and builds tools to enrich your java. You can use it to reduce the boilerplate code for model/data objects or to enhance language functionalities, e.g. it can generate getters and setters for those objects automatically using the Lombok annotations.

Lombok is an annotation processor, which means it uses a Java mechanism that allows executing some tasks before compilation.
Lombok is one of the most popular Java libraries. Why? Let's take a look at its pros and cons.

Getting started

One library is enough to start your adventure with Lombok. Let's take a look at the extract below.

dependencies 
{
    ...
    annotationProcessor "org.projectlombok:lombok"
    implementation "org.projectlombok:lombok"
    ...
}

That's all! Now Lombok is in your project, and all its functionalities are available to use.

Boilerplate code

First of all, Lombok reduces the standard Java code. Generating getters/setters? Providing a toString, equals or hashCode? Now that is no problem!

@Getter
@Setter
@EqualsAndHashCode
@ToString
public class UserAccount {
    private Long id;
    private Long name;
    ...
}  

By adding annotations, with a few lines of code, we can skip generating boring getter/setter, toString, equals, hashCode, constructors, and probably anything else we do every time we create a model. Every time the model is changed, Lombok updates everything for you. What is also important, we reduced the number of code lines in the file.

Below you can see a sample list of available annotations:

  • @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor - designed for generating constructors
  • @Builder - provides builder object for a given class
  • @Data - provides getters for all fields, toString method, and hashCode and equals implementations that check all non-transient fields; also generates setters for all non-final fields, as well as a constructor
  • @Value - similar behavior as @Data, but designed for immutable objects
  • @Slf4j - causes Lombok to generate a logger field

Lombok complexity

Everything looks nice and simple? Unfortunately, the devil is in the details! Lombok has many advantages, but very often you can crash into a wall when the usage of Lombok causes confusion in your project.

Entities

The JPA Entity has several rules that must be followed. Starting with a no-args constructor followed by a valid comparison mechanism, the equals/hashCode method should rely on the identity field(s). Furthermore, one should avoid the situation where some lazy variables are used in a toString method.

For these reasons, we cannot just add @Data annotation to an entity. In that case, it requires some modifications that do not always look nice.

@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@NoArgsConstructor@ToString(onlyExplicitlyIncluded = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@Entity
public class UserAccount {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @EqualsAndHashCode.Include
    @ToString.Include
    private Long id;
    
    @ToString.Include
    private UUID userId;	
    
    @Builder.Default
    private Boolean blocked = false;
    
    ...
}
  • To generate getter/setters - annotations @Getter/@Setters
  • To generate no-args constructor - @NoArgsConstructor
  • To generate proper equals and hashCode - @EqualsAndHashCode(onlyExplicitlyIncluded = true) and next on identity field @EqualsAndHashCode.Include
  • To generate proper toString - @ToString(onlyExplicitlyIncluded = true) and next on each field which should be included in this method @ToString.Include
  • To provide builder - @AllArgsConstructor(access = AccessLevel.PRIVATE) and @Builder annotation and @Builder.Default for each field which should have a default value.

I'm sure we can agree that such an entity is not the most developer-friendly code.

Data Transfer Object when Jackson is use

Another example of Lombok complexity is relates to the DTOs used as a model for REST controllers and when Jackson is responsible for transforming JSON request into objects.

In our project, we try to maintain the convention that all DTO classes used as request/response should be immutable objects. For this reason, the @Value annotation is our companion. Unfortunately, Jackson doesn't like it ;) Jackson, by default, starts creating instances calling default argument. When using @Value annotation, we don't provide any. What is the answer? Again, not the clean one.

@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)))
public class UserCreatedResponseDto {
    private final UUID userId;
}

We must provide repeated usage of @AllArgsConstructor from @Value, with specific parameters: onConstrutor. This construction tells Lombok to create a constructor with all arguments, and will also contain Jackson's specific annotation that will affect Jackson's behavior to change the objects are created. The default mechanism will change to 'properties' (argument(s) for creator will be bound from matching properties of an incoming Object value, using creator argument names).

This design is not the most readable, and easy to replicate out of the head for anyone, but it's necessary when Lombok is in use and we want it to be throughout the entire project.

Immutable object

A quick look at another example where the usage of Lombok can get a bit confusing.

To create an immutable object (mainly DTO) with Lombok, we can use the @Value annotation. At first glance, it gives you all that is necessary for immutable objects: private, final methods, only getters, initialization via a constructor, but can be very dangerous.

We must remember, that the getter for the collection field will not return an immutable version of it. It gives you the collection that we provide. So, to make sure that object is fully immutable, we need to specify its immutable instance in the constructor, as in the example below.

@Value
public class SomeDto {
    private final List users;
}

var someDto =  new SomeDto(List.of(new User()));

Summary

Project Lombok has many pros and cons.

It certainly reduces the boilerplate code, reduces lines of code in the project, speeds up the code development. It is also widely used, still in development, which assures us, that any security or performance vulnerability will find a solution very quickly.

Unfortunately, it has several disadvantages. Some of the usage for annotations isn't straightforward. From the readability point of view, the use of Lombok determines the use of many annotations, which in combination with other frameworks (i.e. Spring, Hibernate) make code difficult to understand. What is also crucial is that the developer doesn't see the code generated by Lombok, cannot find a getter in the code. After all, the use of some annotations (such as @Builder or @EqualsAndHashCode) can get complicated in places where inheritance is in use.