Lombok 在 Java Records 时代仍然相关
Lombok Is Still Relevant in the Age of Java Records

原始链接: https://swaranga.dev/posts/on-lombok/

作者认为 Lombok 尽管受到批评仍具价值。其 cited 的 bug 实为糟糕数据建模与测试缺失,而非 Lombok。Records 无法替代 Lombok 的日志、依赖注入构造器、构建器及其他样板减少。使用数据显示日志与 RequiredArgsConstructor 占主导。编译时生成比 Jackson、Hibernate 等框架的不透明运行时行为更透明。升级担忧被夸大;Lombok 通常快速修补,若真出问题,delombok 可生成源码解除锁定。务实胜过纯粹。

围绕“Lombok 在 Java Records 时代仍具相关性”的 Hacker News 讨论, debate 了减少样板代码的库是否仍然有用。一位评论者认为,随着编码代理能够生成样板代码,Lombok 这类工具正变得过时;另一位反驳说,这种推理会使得所有库都过时。文章作者同意自己对新 Java 特性感到没那么兴奋,因为代理会写代码。另一条评论说,文章凸显了 Java 作为语言的弱点。还有一条评论提到印尼岛屿命名主题,以及 Java、Lombok 和巴厘岛与“dark magic”的关联。
相关文章

原文

Every six months, like clockwork, a new JDK drops—and right on cue, the Java community’s ideological purity tests begin.

The usual suspects crawl out to complain about Project Lombok. They argue that it does “weird hacks” to the javac compiler, that it slows down upgrades, and that we don’t need it anymore because we have Records.

I am a little tired of this constant principled stance against its usage. I am an application developer, not a compiler engineer. I’ve been using Lombok for roughly 15 years now, and I can clearly say its benefits massively outweigh its cons. I’m glad I don’t work with people who hold that dogmatic opinion, because at the end of the day, we are paid to deliver working software—not to coddle the compiler.

I’m still betting on Lombok, and for me, the arguments against it usually fall flat in the real world.

Lombok introduces “weird bugs”

In over a decade and a half of using Lombok, I have only seen exactly one issue where Lombok was called out in a post-mortem as the root cause—and even then, the blame was unfair IMO.

We had a class annotated with @Data containing a primitive boolean field. When deserializing a message from SQS, the incoming payload was missing this field. Because it was defined as a primitive, Jackson assigned it the default value of false, which accidentally triggered a different code path.

The post-mortem labeled it a “Lombok issue.”

I disagree. Lombok simply generated the setter. The true root causes were:

  1. Bad data modeling: The field logically required three valid states (true, false, and null), so it should have been a boxed Boolean type from the start.
  2. Lack of test coverage: This edge case should have been caught in testing immediately, or flagged during the PR review.

Blaming Lombok for a developer coding error and a gap in test coverage is just lazy.

The Myth of “We Have Records Now”

Whenever you defend Lombok, someone inevitably chimes in with, “Just use Records!”

Records are fantastic for immutable data carriers, but they are not a complete replacement for Lombok. To prove this, I did a quick check on my current project to see which Lombok features we actually rely on.

Here is the raw data:

find . -name "*.java" | xargs cat | grep "import lombok" | sort | uniq -c | sort -n -r

    144 import lombok.extern.slf4j.Slf4j;
    131 import lombok.NonNull;
    120 import lombok.RequiredArgsConstructor;
     68 import lombok.Builder;
     30 import lombok.Data;
     24 import lombok.Getter;
     22 import lombok.NoArgsConstructor;
     22 import lombok.AllArgsConstructor;
     12 import lombok.experimental.Accessors;
      8 import lombok.extern.jackson.Jacksonized;
      6 import lombok.EqualsAndHashCode;
      4 import lombok.AccessLevel;
      2 import lombok.Singular;
      2 import lombok.experimental.UtilityClass;
      1 import lombok.ToString;
      1 import lombok.extern.log4j.Log4j2;

What dominates the top of that list is not @Data. It’s logging and code to support dependency injection.

I cannot imagine coding without @RequiredArgsConstructor. In Spring, you just declare your injected services as final fields, add that one annotation, and your constructor boilerplate vanishes. Records do absolutely nothing to solve this. Add @Slf4j and @Builder to the mix, and you are saving hundreds of lines of tedious, error-prone boilerplate per module that you’d otherwise have to write, review, and maintain.

And as for IDEs generating getters and setters? You still have to read that cluttered code. Plus, adding a new field means remembering to manually regenerate the equals and hashCode methods; a step that is easy to forget.

The Lombok “Dark Magic”

Purists argue that Lombok’s AST manipulation is “magic” and that modifying the compiler’s behavior is an architectural sin. But these same developers will happily build entire enterprise systems on top of frameworks that do far worse things at runtime.

Take Jackson, for example. The Java purist wants everything to be explicit and strictly abide by JVM rules. Yet, when Jackson deserializes a JSON payload into an object that lacks a default constructor, it often uses reflection (or libraries like Objenesis) to allocate memory for that object without ever actually calling a constructor. You are upset that Lombok generates a predictable setName() method at compile time, but you are perfectly fine with a framework completely bypassing the JVM’s object-creation lifecycle at runtime?

Or look at Hibernate and JPA. You define a standard List<Order> in your entity and write your business logic against it. But at runtime, Hibernate silently intercepts your object and replaces your standard Java ArrayList with its own PersistentBag proxy. If you try to iterate over that list after the database session closes, your application explodes with the dreaded LazyInitializationException.

That is more opaque, runtime sorcery where standard data structures are hijacked in an invisible manner.

Compared to that, Lombok is incredibly boring. The generated code exists completely at compile time. You can view the .class files. You can see exactly what it generated, and if a getter returns a value, you can step right through it. If we are willing to tolerate the massive, opaque runtime machinery of Jackson and Hibernate because of the velocity they provide, we should absolutely apply that exact same pragmatism to Lombok. AOP is another such tool with lots of gotchas - I remember once debugging a Try-Catch block in an AWS SWF (Simple Workflow) FLow Framework implementation where the Try and Catch blocks were literally executing on different machines.

A future JDK release will completely break Lombok’s compiler hacks

First, the upgrade panic is overblown. Unless you are raring to upgrade on day zero, Lombok is usually patched and ready within days of a major release. (I’m already running JDK 26 with Lombok).

But let’s say the absolute worst-case scenario happens, and Lombok is permanently banned from future JDKs. If that day comes, my migration strategy is incredibly simple:

  • Run delombok on my source code.
  • Commit the generated boilerplate and remove the dependency.
  • Upgrade the JDK in a separate PR.

There is zero runtime lock-in. Until that hypothetical doomsday arrives, I will gladly save my time and skip writing code that I simply don’t have to write. Purity is great in theory, but pragmatism ships features.

联系我们 contact @ memedata.com