返回博客

JDK 25 下 Lombok 1.18.x 静默不生成 getter/setter

在一台开发机上用 mvn clean package 打包一个存量 Spring Boot 项目,终端最后一行印出了熟悉的 BUILD SUCCESS。部署上去,调用接口——NPE。日志显示某个实体字段始终为空,明明代码里写了 entity.setXxx(value),数据库里却什么也没写进去。

这不是业务逻辑 bug,这是构建工具链在骗你。

现象

  • mvn clean package 全程无报错,BUILD SUCCESS
  • 部署到测试环境后,调用写接口,字段值始终为 null
  • 或直接抛出 java.lang.NoSuchMethodError: Entity.setXxx()
  • 本地 IDE 里代码提示正常,看不出任何异常

根因

Lombok 的注解处理器依赖 com.sun.tools.javac.* 内部 API 来生成 getter/setter 字节码。JDK 16 起,JEP 396 正式封闭了这些内部 API(强封装模块),Lombok 1.18.30 以下没有对应的 --add-opens 处理路径,注解处理器在遭遇权限拒绝时悄悄退出,不报错——编译阶段不失败,但生成的 .class 文件里根本没有任何 accessor 方法。

兼容矩阵一目了然:

| Lombok 版本 | JDK 8 | JDK 11 | JDK 17 | JDK 21 | JDK 25 | |------------|-------|--------|--------|--------|--------| | 1.18.8 | ✅ | ⚠️ | ❌ | ❌ | ❌ 静默 no-op | | 1.18.20 | ✅ | ✅ | ⚠️ | ❌ | ❌ | | 1.18.30 | ✅ | ✅ | ✅ | ✅ | ⚠️ | | 1.18.34 | ✅ | ✅ | ✅ | ✅ | ✅ |

坑:BUILD SUCCESS ≠ Lombok 正常运行

mvn 不会因为 Lombok 注解处理器静默退出而报错。同一次构建内 Lombok 已生成过的类文件会被缓存复用,所以 IDE 里看起来 setter 存在,但 mvn clean 后的全新构建字节码里已经没有这些方法了。只有在运行时调用时才会爆出 NoSuchMethodError,或者更隐蔽地:setter 根本没被调用,字段一直是 null

正解

方案 A(推荐:对齐构建 JDK 与运行时 JDK)

存量项目的生产运行时如果是 JDK 8,最安全的做法是让构建也用 JDK 8,消灭构建/运行时版本差异:

# macOS 上安装 Zulu JDK 8
brew install --cask zulu@8
 
# 指定 JDK 8 构建,跳过测试
JAVA_HOME=$(/usr/libexec/java_home -v 1.8) mvn clean package -DskipTests

一个环境变量,不改 pom,对其他模块零影响。

方案 B(若必须使用现代 JDK)

升级 Lombok 到 1.18.34+,并在 maven-compiler-plugin 中显式注册注解处理器路径:

<!-- pom.xml -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.34</version>
    <scope>provided</scope>
</dependency>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.34</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>
正解:构建后用 javap 验证字节码

无论选哪个方案,都应在 CI 流程里加一步字节码验证——检查关键实体类里 setter 确实存在:

# 解包 jar,反编译 class,grep setter 名
unzip -p target/app.jar BOOT-INF/classes/com/example/entity/Order.class \
  | javap -p - \
  | grep -E "setStatus|getStatus"
# 必须出现两行;零行即 Lombok 没有生效

把这一步加进构建脚本,Lombok 静默 no-op 就会在 CI 阶段被拦截,而不是溜到运行时才爆。

一句话外卖

Lombok 在 JDK 16+ 内部 API 封闭后会静默放弃生成——BUILD SUCCESS 只证明编译没出错,不证明注解处理器真的跑了;验证字节码是唯一可信的断言。