Tomcat 掃描 Java JAR 與 module-info.class 的示意圖

Invalid byte tag in constant pool: 19:Tomcat 與 module-info.class 解法

最近把 Log4j 升級到 Log4j 2 後,Tomcat 啟動時出現 Invalid byte tag in constant pool: 19。這個錯誤看起來像是 Log4j 或 Java 執行失敗,實際上多半是 Tomcat 內建的 class 掃描器太舊,無法解析 Java 9 才加入的模組資訊

先說結論:如果堆疊出現 org.apache.tomcat.util.bcel,應優先升級 Tomcat,而不是只替專案加一個新版 commons-bcel。同時確認 Tomcat、JDK 與應用程式的版本彼此相容。

原本遇到的錯誤

這篇文章最初記錄的情境,是升級 Log4j 之後,Tomcat 在部署應用程式、掃描 JAR 內的 class 與 annotation 時,讀到 module-info.class 而中斷。常見的堆疊如下:

org.apache.tomcat.util.bcel.classfile.ClassFormatException:
Invalid byte tag in constant pool: 19
    at org.apache.tomcat.util.bcel.classfile.Constant.readConstant(Constant.java:97)
    at org.apache.tomcat.util.bcel.classfile.ConstantPool.<init>(ConstantPool.java:55)
    at org.apache.tomcat.util.bcel.classfile.ClassParser.readConstantPool(ClassParser.java:176)
    at org.apache.tomcat.util.bcel.classfile.ClassParser.parse(ClassParser.java:85)
    at org.apache.catalina.startup.ContextConfig.processAnnotationsStream(ContextConfig.java:2042)
    at org.apache.catalina.startup.ContextConfig.processAnnotationsJar(ContextConfig.java:1988)
    at org.apache.catalina.startup.ContextConfig.processAnnotationsUrl(ContextConfig.java:1958)
    at org.apache.catalina.startup.ContextConfig.processAnnotations(ContextConfig.java:1912)

如果錯誤前面還有 Unable to process Jar entry [...] from Jar [...],方括號中的路徑通常就是要追查的 class 與 JAR。先把這一行記下來,比直接猜是哪個套件有效。

constant pool 的 tag 19 是什麼?

Java class 檔有一個 constant pool,用來保存類別、方法、字串等資訊。依照 Java Virtual Machine Specification,tag 19 代表 CONSTANT_Module,tag 20 則代表 CONSTANT_Package;兩者都是 Java 9 的 class file format 53.0 才加入。

因此,錯誤不是「tag 19 的資料壞了」,而是執行掃描的解析器只認得較舊的 class 格式。Apache Commons BCEL 也曾記錄相同錯誤,舊版 BCEL 6.1 受影響,並在 6.2 修正。

為什麼使用 Java 7 或 Java 8 仍會讀到 Java 9 的 class?

部分函式庫使用 Multi-Release JAR。JAR 的主要 class 仍可供舊版 Java 使用,但另外放入不同 Java 版本專用的實作,例如:

META-INF/versions/9/module-info.class

正常情況下,舊版 JVM 不會把這個檔案當成一般 class 載入;但舊版 Tomcat 在部署時會主動掃描 JAR 內的 class 與 annotation。如果掃描器沒有正確處理 Java 9 的模組格式,就可能在還沒真正執行應用程式之前先拋出 tag 19。

Apache Log4j 的 LOG4J2-3235 就記錄了相同案例:在 Java 7 使用 Log4j 2.12.2 時,舊版 Tomcat 掃描 META-INF/versions/9/module-info.class 發生此錯誤;該問題最後被判定不是 Log4j 本身的缺陷,而是掃描器的相容性問題。

先定位是哪個 JAR 觸發

  1. 查看完整日誌:搜尋 Unable to process Jar entrymodule-info.classInvalid byte tag,不要只保留最後一段堆疊。
  2. 記錄環境版本:確認實際啟動 Tomcat 的 Java,而不只是命令列預設的 Java。
  3. 檢查可疑 JAR:確認裡面是否有 Java 9 的版本化 class。
  4. 追查相依來源:找出該 JAR 是直接相依,還是由其他套件傳遞引入。
# Java 與 Tomcat 版本
java -version
$CATALINA_HOME/bin/version.sh

# Windows:檢查 JAR 內的 module-info.class
jar tf your-library.jar | findstr /i "module-info.class"

# Linux / macOS
jar tf your-library.jar | grep -i "module-info.class"

# Maven
mvn dependency:tree

# Gradle
./gradlew dependencies

在 Windows 上也可以執行 %CATALINA_HOME%\bin\version.bat。如果服務是由 IDE、Windows Service、Docker 或 CI 啟動,還要檢查該執行環境自己的 JAVA_HOME,因為它可能與終端機不同。

建議的修正順序

1. 優先升級 Tomcat

堆疊中的 package 若是 org.apache.tomcat.util.bcel,代表執行的是 Tomcat 重新封裝的 BCEL。這時只在應用程式的 Maven 或 Gradle 加入新版 commons-bcel,通常不會替換 Tomcat 內部的掃描器。

最直接的做法,是升級到仍受支援、且符合應用程式規格與 Java 版本的 Tomcat。舊的 Java EE 應用程式若仍使用 javax.*,通常應先評估 Tomcat 9;升到 Tomcat 10 或 11 會涉及 javax.*jakarta.* 的遷移,不能只替換安裝目錄。

2. 對齊 JDK、Tomcat 與編譯目標

確認三件事:Tomcat 支援目前的 JDK、應用程式的編譯目標不高於執行環境、相依套件也仍支援該 Java 版本。不要只看到 class 檔與 Java 9 有關,就直接把正式環境換成 Java 9;真正過舊的可能是掃描器。

3. 若不是 Tomcat,再更新實際拋錯的掃描工具

如果 package 是 org.apache.bcel,而不是 org.apache.tomcat.util.bcel,就要檢查專案或工具實際使用的 BCEL 版本。相同原則也適用於其他 bytecode 分析器:更新真正讀取 class 的元件。

4. 必要時暫時調整相依版本

當 Tomcat 暫時無法升級,可以先改用仍支援目前 Java 與容器的套件版本,並把它當成短期相容措施。若涉及 Log4j,版本選擇也必須同時考慮官方安全更新,不建議為了略過掃描錯誤而長期退回已知有風險的舊版。

不建議直接刪除 module-info.class

把第三方 JAR 解壓後刪掉 module-info.class 看似能繞過掃描,但可能破壞 JAR 簽章、可重現建置與後續更新,也會讓同一套程式在不同環境出現不一致。除非是為了短暫驗證原因,否則應修正容器或相依版本,而不是長期維護一個手動修改過的 JAR。

修正後如何驗證

  • 清除 Tomcat 的 worktemp 與舊的已展開應用程式,再重新部署。
  • 確認啟動日誌沒有 Invalid byte tag 或被忽略的 JAR 掃描警告。
  • 確認應用程式與 Log4j 正常載入,並執行一個實際會寫入日誌的功能。
  • 重新執行 dependency tree,確認正式環境使用的確實是預期版本。
  • 把 Tomcat、Java 與關鍵相依版本記錄到部署文件,避免下一次只更新其中一項。

常見問題

這代表 JAR 已損壞嗎?

不一定。若路徑指向 META-INF/versions/9/module-info.class,通常是舊解析器不認得合法的 Java 9 class 格式。若同一 JAR 在相容的 Java 與工具上也無法讀取,才需要再檢查下載或檔案是否損壞。

只升級 JDK 可以解決嗎?

不保證。錯誤來源若是 Tomcat 內建的舊 BCEL,即使 JVM 較新,Tomcat 在掃描時仍可能失敗。應先確認堆疊中的 package,再升級真正執行解析的元件。

一定是 Log4j 造成的嗎?

不是。Log4j 只是常見的觸發案例;任何包含較新 class 格式或 Multi-Release JAR 結構的套件,都可能讓舊掃描器暴露相同問題。

結論

Invalid byte tag in constant pool: 19 的關鍵不是盲目更換 Log4j 或刪除 class,而是先看完整日誌,找到被掃描的 JAR,再確認是哪一個 bytecode 解析器過舊。若堆疊是 org.apache.tomcat.util.bcel,優先升級 Tomcat並對齊 Java、容器與相依套件,通常才是穩定、可維護的解法。

參考資料

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *