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

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法