Java 9以降で JWTトークン発行を実装する際の問題とその解決方法

## 目次
- [はじめに](#はじめに)
- [エラー概要](#エラー概要)
- [エラーメッセージ（例）](#エラーメッセージ例)
- [エラー原因](#エラー原因)
- [解決方法①(XML)](#解決方法xml)
- [解決方法②(JSON/筆者はこちらを採用)](#解決方法json筆者はこちらを採用)
- [まとめ](#まとめ)
- [さいごに](#さいごに)
- [補足解説1](#補足解説1)
- [補足解説2](#補足解説2)



## はじめに
Java21を使用してログイン機能の開発中に、JWTトークンを発行する処理を `jjwt` ライブラリで実装していました。
```
String token = Jwts.builder()
    .setSubject("username")
    .signWith(SignatureAlgorithm.HS256, secretKey)
    .compact();
```
上記のように .compact() を呼び出してトークンを生成しようとした際
以下のエラーが発生
```
java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
```

本記事ではその原因と解決方法について紹介します。

---

## エラー概要
- **エラー内容**：`java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter`
- **発生タイミング**：Java 9以降の環境で JWT ライブラリ（`jjwt`）を使用したとき
- **使用技術**：Java 21 / jjwt 0.9.1（古いバージョン）

---

## エラーメッセージ（例）
```text
Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
	at io.jsonwebtoken.impl.TextCodec.decode(TextCodec.java:99)
	...
```

---

## エラー原因
- Java 9以降では javax.xml.bind パッケージ（JAXB）が標準モジュールから除外された。
- 使用していた`jjwt`は古いバージョンのライブラリであり DatatypeConverter を内部的に使用している。java9以前では、標準モジュールのjavax.xml.bindパッケージ(JAXB)にConverterが含まれていたが、Java9以降は標準もモジュールから除外されたため、Converterも標準で存在しなくなった。
→そのため、jjwtが必要としているConverterが見つからずエラーになった。



---
## 解決方法
解決方法を二つ紹介しています。①がXML形式でデータを扱う方法、②がJSON形式でデータを扱う方法です。
今回使用している`jjwt`はJSON形式でデータを扱うようにできているため、筆者は解決方法の②を選びました。
JavascriptとJSONは互換性が良いようで、現在は機能実装のみですが将来的にはフロントエンドをJavascriptで構成したWEBアプリケーションを作成しようと考えているので、こちらを採用しました。
その他に、アルゴリズムが豊富・JAXBが標準モジュールから除外されたようにモジュールの分離によって軽量化と最適化を生み出すことが期待できそうです。

## 解決方法①(XML)
- javax.xml.bind パッケージを明示的に依存関係を注入する。
```
implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.1'
```
依存関係を注入することで、古いjjwtがXMLとJavaの相互変換を行うことを補完してくれる。
※ポイントは古いjjwtはXMLとJavaを相互変換しようとする点


## 解決方法②(JSON/筆者はこちらを採用)
- 最新のjjwtのバージョンを使用する。
```text
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
```
Jacksonではインタフェースと実装クラスが分かれているため、インタフェースと実装クラスそれぞれの依存関係を注入する必要がある。
※ポイントは新しいjjwtはJSONとJavaを相互変換しようとする点

---

### これだけではまだ未解決!!!
Java9以前ではjavax.xml.bindパッケージが標準モジュールでした。それに伴ってconverterも依存関係を注入することなく使用することができ、日付を文字列に簡単に変換してくれていました。

したがって現在の修正では、Converterのような型変換を行ってくれる機能がない状態になります。

### JSONを通して型変換を行ってくれる外部ライブラリをインポート
#### Jackson（jackson-dataformat-xml）をインポートする。
- JSONでもXMLでも使える。
- 注釈もシンプル。軽量。

```
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
```

Jacksonをインポートすることで、JSONとJavaの相互変換を可能にしてくれる。

---

## まとめ
- Java 9以降では javax.xml.bind パッケージ（JAXB）が標準モジュールから除外された。
これによって、JWTトークンを発行する処理を実装するパターンは3パターン(他にあればぜひ教えてください)
1. Java9以前のバージョンを使用する場合: 問題なく実装可能
2. Java9以降のバージョンを使用する場合 & 古いjjwt(ex 0.9.1)を使う場合: 明示的にJAXBの依存関係を解決 
3. Java9以降のバージョンを使用する場合 & 新しいjjwtを使う場合: jackson使用

---

## さいごに
普段はエラーが出たらすぐにGPT大先生に助けを求めていましたが、こうして一つのエラーを突き詰めて考えてみると、学びが多いです。
今回初めて記事を投稿しましたが、なかなか見やすい記事を書くのは難しいですね、
間違えているところがあればぜひ教えていただきたいです。
よろしくお願いします。

---

## 補足解説1

### ◾ javax.xml.bind パッケージとは？
- Java SE 6〜8 に含まれていた JAXB（Java Architecture for XML Binding） というAPI群。
- 主な目的は、「XMLデータ ⇄ Javaオブジェクト」間の変換（マーシャリング／アンマーシャリング）。
- Java 9 以降では モジュール制御（Jigsaw） の導入に伴い、標準モジュールから除外され、明示的にライブラリとして追加しないと使えなくなった。


### ◾ DatatypeConverter とは？
- javax.xml.bind.DatatypeConverter は、Base64変換、Hex変換、型変換などを提供するユーティリティクラス。
- 例：文字列 ⇄ Base64、日付 ⇄ 文字列 などの変換を簡単に行える。
- JWTなどのトークン処理で Base64 のエンコード／デコードによく使用されていた。


## 補足解説2
### ◾ マーシャリング（Marshalling）とは？
- Javaオブジェクト → XMLやJSONなどの形式に変換すること（書き出し）


### ◾ アンマーシャリング（Unmarshalling）とは？
- XMLやJSONなどのデータ → Javaオブジェクトに変換すること（読み込み）

#### 具体的なイメージ
##### Javaでのオブジェクト
```
User user = new User("Taro", 25);
```

##### マーシャリング（Java → XML）
```
<User>
  <name>Taro</name>
  <age>25</age>
</User>
```

##### アンマーシャリング（XML → Java）
```
User user = new User("Taro", 25); // XMLから再構築された状態
```

---