Project Lombok revisited: still relevant or past its prime?
"I recently revisited Project Lombok to see whether this popular lib remains as essential in modern Java development."
Revisiting Project Lombok made me question how relevant it remains in the context of modern Java development.
First, a quick definition. Project Lombok is a java library that automatically plugs into your editor and builds tools, spicing up your java. (See official docs). In plain words, you annotate your Java classes, and the lib takes care to augment your code and generates boilerplate at compile time. It sounds elegant, but let’s go through its core annotations and evaluate their actual relevance today.
Lombok’s annotations review#
@ToString. Generates a toString() impl. based on all fields by default, similar to Java records. Mostly useful for DTOs, but may cause noticeable overhead when applied to deeply nested objects, especially in logs.
@Getter / @Setter. Generates accessor methods for fields. Once valuable before records (Java 16), now mostly relevant for genuinely mutable DTOs. Since most DTOs today are designed to be immutable, setters offer limited practical value.
@*Constructor. Generates default or all-args constructors. Useful for controllers/services with field-based dependencies, but overuse can hide excessive coupling and break SRP (Single Responsibility Principle) - explicit constructors, though longer, better express intent.
@EqualsAndHashCode. Generates equals() and hashCode() using all fields by default. Useful for DTOs in hash-based collections or objects compared by equals, but such cases are rare - manual impl. is often clearer and more intentional.
@Data. A composite annotation that bundles several others above with sensible defaults. Convenient, but often excessive - most classes don’t need everything it generates, and overuse can lead to unnecessary code, reducing readability and clarity of intent.
@NonNull. Inserts runtime checks (guard statements) for null arguments in methods, adding throws statements at the beginning on the methods. With JSpecify introducing standardized compile-time nullability annotations (@NonNull / @Nullable), Lombok’s approach now adds little practical value. See my detailed analysis.
@SneakyThrows. Wraps a method body in a try-catch block to bypass checked exceptions. One of the least useful annotations today - modern libraries rarely use checked exceptions, e.g. Spring framework replaced them with runtime exceptions long ago (starting from v.2). Personally, I try to avoid checked exceptions too in my code, for the same reason: in most cases there is no way to properly process them at runtime, except for re-throwing a RuntimeException.
@Slf4j. One of the few genuinely useful annotations — it saves a line of code by automatically adding a class-level logger. However, IDEs can generate this just as easily (e.g. Intellij offers a feature called “Live Template”), and most classes don’t need logging beyond local debugging.
@Builder. A genuinely useful annotation for creating DTOs with fluent, composable, and named setters. Unfortunately, records don’t support builders natively, though several lesser-known tools can provide similar functionality, e.g. this community-driven GitHub “record-builder” repository offers exactly this.
This isn’t an exhaustive list, but it covers the features most commonly used in real-world projects. As we can see, many of them are fairly easy to replace or even avoid altogether.
The hidden cost of Lombok’s dependency#
Every dependency you add increases the system’s entropy. Bugs or inconsistencies buried inside those libraries can cascade into your own production code. That’s why it’s worth carefully weighing the trade-off — the convenience a library provides versus the complexity and fragility it silently introduces. In case of Project Lombok, things are even more interesting. Let’s have a closer look at how it processes its annotations.
Libraries like MapStruct or JPA’s Static Metamodel stay within the boundaries of JSR-269 — the official, standardized annotation-processing API. Under this model, processors operate in a controlled environment: they can scan annotations, read code structure, and generate entirely new source files via the Filer API. What they cannot do is modify existing code. The contract is strict — read and generate, never rewrite. To get an idea how it works, there are community articles explaining the API with examples.
Lombok, however, goes further than that. It doesn’t just observe annotations; it actively injects and rewrites code inside the compiler. To achieve this, Lombok hooks directly into javac’s internal API (com.sun.tools.javac.*), gaining access to the compiler’s AST (Abstract Syntax Tree) — the in-memory representation of your Java code after parsing. Once inside, it uses reflection to patch the tree before the compiler’s later stages (like type attribution and bytecode generation) take over.
This means Lombok effectively becomes part of the compiler pipeline itself. It inserts getters, constructors, and builders before the compiler ever sees your final code. While powerful, this approach relies on undocumented and unstable compiler internals, which shift between JDK releases — a key reason why Lombok often breaks with new Java versions and needs constant maintenance. This video explains those hacks in-depth.
In short, while JSR-269 processors collaborate with the compiler, Lombok intervenes in it.
At the moment of writing this post, official Lombok’s GitHub repository had over 900+ opened issues, with typical one being something like: “Lombok not generating code with project migrated to SpringBoot version X”. This number of issues is actually a symptom of Lombok’s fragile architecture.
My verdict#
Ultimately, the decision to include Project Lombok in a modern Java codebase — especially in the era of records and robust standard annotation-processing tools — should be made thoughtfully. For quick prototypes or demo projects, Lombok can still accelerate development and reduce boilerplate. However, from a long-term and strategic standpoint, I would rather rely on standard Java features, minimizing external dependencies even at the cost of a slight drop in short-term productivity. Given the above-mentioned arguments against it, I’d think carefully before introducing Lombok into a new codebase.