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 | ✅ | ✅ | ✅ | ✅ | ✅ |
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>无论选哪个方案,都应在 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只证明编译没出错,不证明注解处理器真的跑了;验证字节码是唯一可信的断言。