Error [java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException]

NoClassDefFoundError: javax/xml/bind/JAXBException の直し方(Java 11 以降で JAXB が JDK から消えた)

FIX SUMMARY verified
Applies when
eclipse-temurin:17-jdkJEP 320 removed the Java EE modules in Java 11; a class built for Java 8 fails at runtime on 17

Verified: reproduced in eclipse-temurin:17-jdk, then the java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException signature was gone after the fix (exit 0).

Java 8 では動いていたアプリを Java 11 や 17 で起動すると、次のエラーで落ちることがあります。

Error: Unable to initialize main class App
Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException

コピペ検索用の表記ゆれです。原因はどれも同じです。

  • java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
  • NoClassDefFoundError: javax/xml/bind/JAXBContext
  • NoClassDefFoundError: javax/xml/bind/DatatypeConverter
  • java.lang.ClassNotFoundException: javax.xml.bind.JAXBException

javax.xml.bind(JAXB)は Java 8 までは JDK に同梱されていましたが、Java 11 で JDK から削除されました。同じクラスファイルが、8 では見つかり、11 以降では見つからない。コードは一切変わっていません。

直し方の要点を先に

JAXB を依存として足します。ただし版を間違えると直りません。

足す依存結果
jakarta.xml.bind-api 2.3.xjaxb-runtime 2.3.x直るimport javax.xml.bind.* のまま)
jakarta.xml.bind-api 4.x(最新)+ jaxb-runtime 4.x直らない(同じ NoClassDefFoundError が出続ける)

「依存を足すだけ」で終わっている記事が多いのですが、最新版を足すと直りません。理由は下の「版を間違えると直らない」で説明します。

なぜコンパイルは通るのに、実行時だけ落ちるのか

Java 11 の JEP 320 が、JDK から Java EE 由来のモジュールをまるごと削除しました。JAXB(java.xml.bind)はその 1 つです。Java 8 の JDK にはこれらが入っていたので、import javax.xml.bind.JAXBContext と書くだけで動きました。

引っかかるのは、ビルドは成功してしまうことです。

  • Java 8 向けにビルドした jar をそのまま新しい JVM で動かす場合:クラスファイル自体は Java 17 でも読めます。読めるので起動はして、javax.xml.bind.JAXBException を解決しようとした瞬間に NoClassDefFoundError になります。
  • javac --release 8 でビルドしている場合:新しい JDK でも、--release 8 は Java 8 当時の API 一覧を参照するため javax.xml.bind のコンパイルが通ります。通るのは実行時の JDK に JAXB があることを意味しません。

つまり「ビルドが通ったから移行できた」と判断できません。JEP 320 の削除は、ビルドではなく実行時に効きます。 CI のビルドが緑のまま、デプロイ先で落ちるのはこのためです。

版を間違えると直らない

依存の artifactId は、どちらの版も同じ jakarta.xml.bind-api です。中身の名前空間だけが版で変わります。

jakarta.xml.bind-api の版パッケージ名import javax.xml.bind.* のコードは
2.3.xjavax.xml.bind動く
3.x 以降(4.x を含む)jakarta.xml.bind動かないjavax.xml.bind は含まれない)

artifact の名前が先に jakarta へ改名され、中身の名前空間の変更はあとから来ました。そのため「jakarta という名前だから新しい JAXB だろう」と最新版を入れると、javax.xml.bind.JAXBException はどこにも無いままで、エラーは 1 文字も変わりません。依存を足したのにエラーが同じなら、まず版を疑ってください。

import を書き換えずに動かすなら、2.3.x を指定します。

Maven:

<dependency>
  <groupId>jakarta.xml.bind</groupId>
  <artifactId>jakarta.xml.bind-api</artifactId>
  <version>2.3.3</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
  <version>2.3.3</version>
</dependency>

Gradle:

implementation 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3'
runtimeOnly 'org.glassfish.jaxb:jaxb-runtime:2.3.3'

API だけでは足りません。JAXBContext.newInstance() は実装を実行時に探すので、jaxb-runtime も必要です。API だけ足すと、NoClassDefFoundError は消えたあとに「JAXB 実装が見つからない」という別の失敗に変わります。

移行しきるなら jakarta へ

2.3.x は「javax のまま延命する」ための選択です。移行を進めるなら、import を書き換えて 4.x を使います

// Before
import javax.xml.bind.JAXBContext;
import javax.xml.bind.annotation.XmlRootElement;

// After
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.annotation.XmlRootElement;

依存も 4.x に揃えます。

Maven:

<dependency>
  <groupId>jakarta.xml.bind</groupId>
  <artifactId>jakarta.xml.bind-api</artifactId>
  <version>4.0.2</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
  <version>4.0.5</version>
</dependency>

Gradle:

implementation 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.2'
runtimeOnly 'org.glassfish.jaxb:jaxb-runtime:4.0.5'

import と依存は、必ず同じ名前空間の組で使います。 import javax.xml.bind.* なら 2.3.x、import jakarta.xml.bind.* なら 4.x です。この記事の冒頭で「最新の 4.x を足しても直らない」と書いたのは、4.x が欠陥品だからではありません。importjavax のまま、中身が jakarta の依存を足していたからです。組で揃えれば、どちらの版でも同じ処理が動きます(import 行だけが違う同じコードを、2.3.3 と 4.0.2 の両方で実行して確認しました)。

どちらを選ぶかは、周辺のライブラリが javaxjakarta のどちらを要求するかで決まります。混在させると、片方の名前空間のクラスだけが見つからない状態になります。 Jakarta EE 9 以降を前提にしたフレームワーク(Spring Boot 3 系など)に載せるなら、jakarta へ寄せるほうが後の衝突が少なくなります。

「—add-modules java.se.ee」は Java 11 以降では通りません

検索すると、次の指定を勧める回答が上位に残っています。

java --add-modules java.se.ee -jar app.jar

Java 11 以降では通りません。 実行すると、元のエラーとは違うエラーになります。

Error occurred during initialization of boot layer
java.lang.module.FindException: Module java.se.ee not found

--add-modules java.xml.bind も同じで、Module java.xml.bind not found になります。

これは Java 9 / 10 の時期の回答です。当時これらのモジュールは JDK に残っていて、既定で無効化されていただけだったので、--add-modules で有効化し直せました。Java 11 はモジュールごと削除したので、有効化する対象がもう存在しません。JVM のフラグでは直せず、依存として持ち込むしかありません。

同時に消えた他のパッケージ

JEP 320 は JAXB だけを消したわけではありません。同じ理由・同じ形で落ちます。

パッケージ実行時に出るもの足す依存の例
javax.activationNoClassDefFoundError: javax/activation/DataHandlercom.sun.activation:jakarta.activation:1.2.2
javax.xml.ws(JAX-WS)NoClassDefFoundError: javax/xml/ws/Servicejakarta.xml.ws:jakarta.xml.ws-api:2.3.3API のみ
javax.annotationNoClassDefFoundError: javax/annotation/Resourcejakarta.annotation:jakarta.annotation-api:1.3.5

この列は NoClassDefFoundError に出ているクラスを解決するための artifact です。JAXB と同じく、API だけでは実行時に実装が見つからないことがあります(JAX-WS なら実装も要ります)。どの実装を足せば動くところまで行くかは、この記事では確認していません。

javax.annotation.Resource は、使い方によって落ちたり落ちなかったりします

  • フィールドに @Resource を付けているだけの場合は、例外が出ません。JVM は解決できないアノテーションを黙って捨てるので、getAnnotations() は 0 件を返し、プログラムはそのまま動きます。
  • Resource.class のように型を直接名指しすると、NoClassDefFoundError になります。

アノテーションとしてしか使っていない箇所は、例外にならないまま、そのアノテーションが付いていないのと同じ状態になります。実測したのは素の JDK のリフレクション(getAnnotations() が 0 件を返す)までで、フレームワークがこの状態をどう扱うかは確認していません。

切り分け

  • 依存を足したのに、エラーが 1 文字も変わらない → 版が 3.x 以降になっています。javax.xml.bind を含むのは 2.3.x までです。
  • NoClassDefFoundError は消えたが、今度は JAXB 実装が見つからないと言われるjakarta.xml.bind-api(API)だけを足しています。org.glassfish.jaxb:jaxb-runtime を追加してください。
  • --add-modules を足したら FindException: Module ... not found になった → Java 9 / 10 向けの古い回答です。上記のとおり、Java 11 以降では消せません。フラグを外し、依存として足してください。
  • ビルドは通るのに、デプロイ先でだけ落ちる → まさにこのエラーの形です。JEP 320 の削除は実行時に効くので、ビルドが緑でも判断材料になりません。実行する JVM の版(java -version)を確認してください。
  • javax.xml.bind ではなく javax.xml.parsersjavax.xml.transform で落ちる → これらは JDK に残っています(削除されていません)。別の原因なので、この記事の対象ではありません。
  • エラーが UnsupportedClassVersionError: class file version ... → クラスが見つからないのではなく、クラスファイルの版が実行 JVM より新しいというエラーです。原因も対処も違うので、UnsupportedClassVersionError: class file version 61.0 の直し方 を参照してください。

検証環境

  • eclipse-temurin:17-jdk、ネットワーク有り(Maven Central から jar を取得)
  • 再現:javac --release 8 でビルドしたクラスを Java 17 で実行し、NoClassDefFoundError: javax/xml/bind/JAXBException が出て終了コード 1
  • 修正:同一のソースを同じ javac --release 8 でビルドし、jakarta.xml.bind-api 2.3.3 と jaxb-runtime 2.3.3 を実行時クラスパスに足すだけで終了コード 0・シグネチャ消滅。reproduce/fix/.java に差分はありません(変えたのはクラスパスだけです)
  • 「4.x を足しても直らない」「importjakarta に書き換えれば 4.x で通る」「--add-modules java.se.ee / java.xml.bindFindException になる」「javax.activation / javax.xml.ws / javax.annotation が同じ形で落ちる」「@Resource はアノテーションとして付けるだけなら例外にならない」は、いずれも同じイメージで個別に実行して確認しました
  • javac --release 17 では package javax.xml.bind does not exist になり、コンパイルの時点で止まることも確認しています

Maven / Gradle のビルド定義に依存を書く手順は、機構が同じ(実行時クラスパスに JAXB があるか)ことを根拠にした対処であり、ビルドツール経由での再現は通していません。Spring などのフレームワークが @Resource の欠落をどう扱うかは確認していません。

検証(machine-verified)

この修正は eclipse-temurin:17-jdk のバージョン固定コンテナ内で再現し、修正後に java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs jvm/javax-xml-bind-noclassdeffound
● reproduce java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException present ✓
● apply fix exit 0
● re-run java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException gone ✓
PASS verified · eclipse-temurin:17-jdk · signature gone