Why you should not use Lombok

Bruna Pereira
5 min readSep 24, 2019

Versão em português aqui.

Lombok is a very popular library in the Java world that aims to help you write less repetitive code. Many people who are opposed to the use of this tool say that if you use a good text editor, you can take advantage of autocompletes and then you won’t need Lombok.

Personally, I think Java is a VERY verbose language; I see Lombok more as a way of “hiding” all of these implementation details than actually saving time. After all, we spend a lot more time reading codes than writing them!

The benefits of using this tool are well described on the documentation website. So what I want with this article is to be the counterpoint and tell you the reasons why you should not use Lombok! I will divide these reasons by notes, focusing on the most common. Let’s do it!

@Getter

This is the most comfortable annotation. Why not use it?
getters, in any language, break the Tell don’t ask principle. Instead of taking some attribute and doing some logic, try moving that logic into the domain object. Example:

// Code that breaks the tell don't askclass Transaction {
private final Instant timestamp;
private Instant getTimestamp() {
return timestamp;
}
}
public class TransactionsService {
public Transaction retrieveTransaction(int id) {
Transaction transaction = findById(id);

if (transaction.getTimestamp() > Instant.now()) {
// do something
}
.....
}
}

An alternative would be writing it like this:

class Transaction {
private final Instant timestamp;
private boolean isInTheFuture(Instant now) {
return timestamp > now;
}
}
public class TransactionsService { public Transaction retrieveTransaction(int id) {
Transaction transaction = findById(id);
if (transaction.isInTheFuture(Instant.now)) {
// do something
}
.....
}
}

Of course there will be cases where we will need the getters, but in these cases we can just jot down the required attributes with @Getterinstead of jotting down the whole class. That is, instead of:

// All attributes are exposed through getters@Getter
class Transaction {
private final Instant timestamp;
private final String ownerName;
}

The alternative would be:

// Only the attribute ownerName is exposedclass Transaction {
private final Instant timestamp;
@Getter
private final String ownerName;
}

@Setter

Mutable objects are the leading cause of side effects and invalid states, and often difficult to debug. This article talks about the concept of immutability and its pros and cons.
I always have this concept in mind and do my best to make sure that the code I write guarantees it. Most of the time, if we think about the nature of the object, it makes perfect sense that they do not change at all and instead, we create another. If strictly necessary, use @Setter, as well as @Getter, and write down only the parameters, rather than writing down the whole class.

@ToString

This annotation is used 99% of the time for debugging and/or testing. The tip here is: only keep this annotation if you really need it in production. The truth is, we almost never need the method the way Java implements; Usually we override the class toString to best suit our needs.

@AllArgsConstructor

Nothing against it, actually I use it a lot! Sometimes it hides the need to break its class into smaller attributes because we don’t see that giant constructor. Another point to consider is that the order of parameters in the constructor varies according to the declaration of class attributes. So if you have too many primitive types in the builder, you can make a mistake and the compiler will not warn you that something is wrong (hopefully you will have automated tests that cover this scenario).

@Builder

There are some cases (usually when we are still dealing with DTOs and not with domain objects) where our objects are very large and that’s why it is worth instantiating them with a Builder, because the builder gets too big and the code messy. I avoid using it for any domain object. Usually domain objects are small (or should be) and @Builderhas a very dangerous behavior: it allows objects to be created in invalid states. Let me explain.

We have the following class:

class Person {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
....
}
public Person(String firstName) {
....
}
}

By the constructor, we see that it’s not possible for a person to have a lastName but not a firstName. Once we annotate this class with @Builder, nothing stops me from doing this:

Person benSilver = Person.builder().lastName("Silver").build();

This code compiles and we cannot prevent the creation of the invalid object. This is a caution that prevents many NullPointerException errors: preventing domain objects from being created in an invalid state. Therefore I do not recommend using Builder for such cases.
Another very common use for Builders is for creating test objects. In this article I talk a little bit about Lombok’s @Builderalternative for test data creation and how writing production code for tests purpose only is not a good practice.
Remember: Once compiled, Lombok generates real code, which is available in production code.

@NoArgsConstructor

The reasons for not using this annotation are very similar to the case of @Builder: it allows object creation in invalid state. And how else can you add values to this class if not using the also discussed @Setter?
Use it only if it makes sense to your object that it exists with all empty values. Another scenario where @NoArgsConstructoris needed: when we are using the object as Response and doing object translation for json with Jackson. In this case there is no way to get away from the empty constructor, but you can get away from it on your domain objects using this the technique of creating a different representation for response classes.

@EqualsAndHashCode

It’s very convenient to avoid overwriting the equals and hashCode methods, but the only counterpoint I leave here is: If you’re using JPA to map database entities, be careful about overriding these methods! For more information, here is a great article about it.

@Data

This annotation is a compilation of the @Getter, @Setter, @RequiredArgsConstructor, @ToString and@EqualsAndHashCode annotations. Well, if even after all the reasons explained above about each of these annotations, you need all these features in one class, I believe it’s better if I put them all explicitly. First, because it helps code reading and second, because it gives you the flexibility to add and remove annotations when needed.

@Value

This annotation is an immutable version of@Data. In addition to generating all annotations that @Datagenerates (except @Setter), annotation also transforms all attributes and the class into final. Although quite convenient, as in the previous case, we can achieve the same behaviour with individual annotations. To make the class and attributes final, you can use @FieldDefaults(makeFinal=true)

There are reasons to use (almost) all features of Lombok, however the appeal here is: use it wisely! :)

--

--