Common Lombok Annotation Pitfalls

We’ll look at some commonly used Lombok annotations, how they can be misused, and how we can avoid these risks.

@AllArgsConstructor, @RequiredArgsConstructor, @NoArgsConstructor

These annotations may lead to treatment of constructors as boilerplate, where there wasn’t.

Instance management in Java requires careful thought.

This may manifest itself as:

  • Introducing a constructor where a factory method or another creation pattern should be.

  • Skipping validations during object construction.

Although dependency injection (DI) mitigates some of these concerns, not all the fields will be managed by DI.

Furthermore, @RequiredArgsConstructor can obscure which fields are included in the constructor, as Lombok applies its own logic to determine fields to use as constructor arguments.

@NoArgsConstructor replaces one line of code with another, making its usefulness questionable.

How to manage:

  • Focus on thoughtful object creation design rather than automatically delegating it to Lombok.
  • Use @RequiredArgsConstructor cautiously, keeping future refactoring and maintainability by other developers in mind.

@Data, @Setter, @Value, @Getter, @EqualsAndHashCode

The habitual use of @Data and @Setter may introduce unnecessary mutability. Listed annotations can also demote the use of Records, both of which can negatively impact the software design.

Besides JPA and some older APIs (such as AWS SDK 1.x), valid cases for mutability are rare. Alternatives to JPA, that work with records —such as Spring Data JDBC- are worth considering, if extended features of JPA are not required.

When immutability is used, records should be preferred -which cover @EqualsAndHashCode and @ToString as well- , unless we are working with a pre-Java 16 version.

Note About Records

Records are not introduced just to provide shorter syntax, but to bring new semantics.

They are named tuples. This brings several benefits:

  • True immutability without reflection backdoor.
  • Efficient serialization and memory usage, especially in collections and streams.
  • Can be used in pattern matching, and more features in the future, like object decomposition, withers etc.

How to manage:

  • Think twice before using @Data or @Setter; immutability is often the better design choice, which makes records more suitable.
  • For most of the time, records are preferable to these annotations.

@SneakyThrows

As noted in the Lombok documentation:

“This somewhat contentious ability should be used carefully, of course”.

@SneakyThrows should only be used in the cases outlined in the Lombok documentation.

One common misuse is using it to ‘convert’ checked exceptions into unchecked ones, which it doesn’t actually do. Thrown exceptions won’t be caught by an upstream RuntimeException handler. Even if it were to convert checked exceptions, it cannot be applied to lambdas, where it would be most useful.

Furthermore, @SneakyThrows relies on unspecified JVM behavior to function.

Me, and many software engineers agree that Java would be better without checked exceptions, but @SneakyThrows is not the solution for that.

How to manage:

  • If it adds value to your project, adhere to the cases outlined in its documentation

@Slf4j,@Log4j2,@Log etc.

Since these replace one line of code with another, the productivity gains they offer are debatable.

Other Annotations

The risks discussed above are based on my on-field experience and often stem from our developer habits.

Except @Builder and @With (discussed below), I’ve rarely come across usage of other Lombok annotations, therefore, I won’t draw conclusions about their risks.

Reviewing the Lombok’s documentation can be useful to assess their benefits vs. risks.

@Builder, @With and Upcoming Java Features

These, in my experience, are useful and safe to use. However, it’s worth noting:

  • Native withers for records are in development: JEP-468.
  • A common use for @Builder is to make constructors with multiple primitive types safer to use. This can also be handled by using custom types for arguments, reducing “primitive obsession”. Project Valhalla’s value classes, can promote this approach with offering lightweight objects. This may be more performant approach, reducing the need for builders - perhaps a topic for another article.

Hidden Complexity and Debugging Challenges

Lombok’s generated code can complicate debugging, especially when breakpoints are needed in the generated sections. Annotations such as @AllArgsConstructor, @Getter and @Locked are common examples where debugging can become more complex.

Integration with Frameworks

While Lombok operates at compile time and manipulates the abstract syntax tree (AST) rather than bytecode, it can still occasionally interact unexpectedly with other frameworks that rely on reflection or runtime proxies, such as dependency injection frameworks, AOP libraries, or circuit breakers like Hystrix.

For instance, I once experienced an issue where an older version of Lombok interfered with circuit breaker annotations, silently disabling them. These cases are rare, but worth noting when using Lombok alongside complex frameworks.

How to handle:

  • Keep Lombok and framework versions up to date.
  • Test critical integrations, especially with frameworks that rely on reflection or proxies.

Future Technical Debt

The Java development team is evolving Java rapidly, yet in a planned, structured way. We should not assume the features offered by Lombok are not considered by them. Unlike Kotlin interoperability, compatibility with Lombok is rarely a consideration during Java’s development.

Furthermore, Java is not designed to prioritize concise abstractions at the expense of clarity. It was conceived to meet the practical industrial needs in a teamwork. For example, Java records are named tuples, unlike Scala’s Tuple2, Tuple3 etc. or Kotlin’s Pair.

How to handle:

  • Keep an eye on future development path of Java.
  • Use Lombok with good knowledge and consideration on Java’s evolving features and philosophy.

Conclusion

We might wonder if all these risks mean we should remove Lombok from our projects. Doing so may not necessarily improve design or maintainability of our project.

Many of the mentioned risks stem from our habitual, hurried coding. Concise code may create a sense of cleanliness and improved productivity, but compared to benefits of a good design, this effect is rather small. No automated code generator can provide a good design consistently.

Lombok provides set annotations, with unrelated purposes and varying usefulness and risks. Therefore, we can utilize Lombok best, when we know it well and consider the risks and benefits in the context of our project and team.