StackTraces
Java已验证

Java NoSuchMethodError 依赖版本冲突的定位

@debug_master32 天前更新阅读 6 min00

编译能过、运行崩溃,是 NoSuchMethodError 最迷惑人的地方。它不是说你的方法拼错了,而是:编译时用的库版本有这个方法,运行时加载的却是另一个没有该方法的版本。类存在,方法不存在,所以报的是 NoSuchMethodError 而不是 NoClassDefFoundError。

Issue & Env(案发现场与环境)

  • 现象:代码在 IDE 里编译通过,运行或部署到服务器后启动/调用时报错,逻辑本身看起来没有语法问题。
  • 环境:Java 8+,Maven / Gradle 工程,常出现在直接或间接依赖发生版本冲突时。

StackTrace(堆栈日志)

JDK 11 之后,报错里带完整方法签名:

Exception in thread "main" java.lang.NoSuchMethodError:
'void com.google.gson.GsonBuilder.setStrictness(com.google.gson.Strictness)'
    at com.example.json.JsonConfig.createGson(JsonConfig.java:24)
    at com.example.utils.JsonUtils.newParser(JsonUtils.java:41)
    at com.example.app.ParserFactory.build(ParserFactory.java:17)
    at com.example.app.ReportApp.main(ReportApp.java:10)

也有旧式不含引号的写法:

java.lang.NoSuchMethodError: org.apache.commons.lang3.StringUtils.isBlank(Ljava/lang/CharSequence;)Z
    at com.example.util.ValidateUtil.isBlank(ValidateUtil.java:18)
    at com.example.service.UserService.validate(UserService.java:47)
    at com.example.controller.UserController.register(UserController.java:33)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:498)

关键在于:它告诉你的不是「类不存在」,而是「某个特定签名的方法在运行时加载的类里找不到」。

Reproduce(复现代码)

场景很典型:service-app 同时依赖 library-b 和 library-c,二者分别传递引入了 common-utils 的不同版本。

service-app
 ├─ library-b └─ common-utils:2.0
 └─ library-c └─ common-utils:1.4

library-b 用到了 common-utils 2.0 才有的 getName() 方法:

// 编译时:classpath 里是 common-utils 2.0,有 getName()
String name = someUtils.getName();

但最终打包/运行时,common-utils:1.4 被 Maven 的「最近优先」或 Gradle 的「最高版本」策略选中,方法就没了,运行抛 NoSuchMethodError。

Root Cause(根因分析)

Java 字节码在记录方法调用时,存的是完整签名:类名 + 方法名 + 参数类型 + 返回类型。编译阶段 javac 对着某一个版本的库解析出这个签名,写进 .class。到了运行期,JVM 按同样的名字和签名去当前 classpath 里找方法,找得到就调,找不到就抛 NoSuchMethodError。

编译和运行看到的是两套 classpath,这是问题根源。Maven / Gradle 的依赖解析规则(Maven 取树中最近的那个,Gradle 默认取最高版本)会决定最终只有一个版本进入运行时,但它未必是编译时用的那个。很多人只在自己的 pom.xml 里声明了一个版本,却没注意到某个间接依赖偷偷把另一个版本拖了进来,导致运行时被顶替。

The Fix(解决方案)

先别改业务代码,先看依赖树,找出同一个构件出现了几个版本:

mvn dependency:tree -Dverbose
# Gradle 用:
# ./gradlew dependencies
# 或定位具体构件:
# ./gradlew dependencyInsight --dependency common-utils

找到冲突后,用 <dependencyManagement>(Maven)或 resolutionStrategy.force(Gradle)把版本锁成一致的,让编译和运行用同一个版本:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.example</groupId>
      <artifactId>common-utils</artifactId>
      <version>2.0</version>
    </dependency>
  </dependencies>
</dependencyManagement>

对确定不要的错误传递依赖,直接在引入处排除:

<exclusions>
  <exclusion>
    <groupId>com.example</groupId>
    <artifactId>common-utils</artifactId>
  </exclusion>
</exclusions>

Spring Boot 项目优先通过 BOM 的 <properties> 覆盖版本,而不是到处写裸版本,避免多个相关依赖被拆散。改完依赖一定做一次干净构建:

mvn clean package

因为增量构建可能残留引用旧方法签名的字节码,clean 能避免这种「改了版本还报一样的错」的假象。

References(参考来源)

最后更新于 2026-09-09

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法