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/JAXBExceptionNoClassDefFoundError: javax/xml/bind/JAXBContextNoClassDefFoundError: javax/xml/bind/DatatypeConverterjava.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.x + jaxb-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.x | javax.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 が欠陥品だからではありません。import が javax のまま、中身が jakarta の依存を足していたからです。組で揃えれば、どちらの版でも同じ処理が動きます(import 行だけが違う同じコードを、2.3.3 と 4.0.2 の両方で実行して確認しました)。
どちらを選ぶかは、周辺のライブラリが javax と jakarta のどちらを要求するかで決まります。混在させると、片方の名前空間のクラスだけが見つからない状態になります。 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.activation | NoClassDefFoundError: javax/activation/DataHandler | com.sun.activation:jakarta.activation:1.2.2 |
javax.xml.ws(JAX-WS) | NoClassDefFoundError: javax/xml/ws/Service | jakarta.xml.ws:jakarta.xml.ws-api:2.3.3(API のみ) |
javax.annotation | NoClassDefFoundError: javax/annotation/Resource | jakarta.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.parsersやjavax.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-api2.3.3 とjaxb-runtime2.3.3 を実行時クラスパスに足すだけで終了コード 0・シグネチャ消滅。reproduce/とfix/の.javaに差分はありません(変えたのはクラスパスだけです) - 「4.x を足しても直らない」「
importをjakartaに書き換えれば 4.x で通る」「--add-modules java.se.ee/java.xml.bindがFindExceptionになる」「javax.activation/javax.xml.ws/javax.annotationが同じ形で落ちる」「@Resourceはアノテーションとして付けるだけなら例外にならない」は、いずれも同じイメージで個別に実行して確認しました javac --release 17ではpackage javax.xml.bind does not existになり、コンパイルの時点で止まることも確認しています
Maven / Gradle のビルド定義に依存を書く手順は、機構が同じ(実行時クラスパスに JAXB があるか)ことを根拠にした対処であり、ビルドツール経由での再現は通していません。Spring などのフレームワークが @Resource の欠落をどう扱うかは確認していません。