Claude CodeでSpring Boot 3へ移行する — jakarta置換とテスト往復
Spring Boot 2から3への移行を、javax→jakartaの一括置換(OpenRewrite)と、残る手直し(Claude Code)に分け、Stop hookのmvn testで往復検証する手順です。
Spring Boot 2から3への移行は、作業の性質が違う3つの層に分けると進めやすくなります。機械的に置換できる層、置換後にテストが教えてくれる層、人かClaude Codeが判断して直す層です。
最初の層をClaude Codeに手で書き換えさせると、差分が大きくなるだけでなく、取りこぼしも出ます。そこで、javax.* からjakarta.* への置換はOpenRewriteのレシピに任せます。Claude Codeには、その後に残る層を直させます。往復の検証には、停止時に mvn test を走らせるStop hookを使います。
3つの層をどう分けるか
| 層 | 中身 | 担当 |
|---|---|---|
| 機械で置換できる | 中身importの javax.* → jakarta.*、依存の座標、プロパティの改名など | 担当OpenRewrite |
| テストが教えてくれる | 中身コンパイルエラー、起動失敗、JPA・セキュリティ周りの挙動差 | 担当Stop hookの mvn test |
| 判断して直す | 中身レシピが触らない独自設定、バージョンを固定した外部依存、テストの修正 | 担当Claude Code |
Spring Boot 3.0はJava 17以上を要求し、Jakarta EE 10に合わせてjavaxからjakartaのパッケージへ移ります。移行ガイドも、変換の道具としてまずOpenRewriteのレシピを挙げています。
この記事の流れは、レシピで一括変換し、Claude Codeとテストの往復で残りを潰す、という順です。新規にAPIを作る流れはClaude CodeでSpring Bootアプリを作るで扱っているので、ここでは既存コードの移行に絞ります。
移行前に揃える前提
置換を始める前に、次の4点を確認します。どれも、後から見つかると原因の切り分けが難しくなります。
移行を始める前の確認
- 1
2.7.x の最新に上げる
移行ガイドは、先に2.7.xの最新版へ上げることを求めています。最新の依存に対してビルドが通る状態を、出発点にします。
- 2
Spring Security は 5.8 を経由する
Spring Securityの移行ガイドは5.8を用意しています。Boot 3.0に上げる前に5.8へ上げ、そこから6.0の手順に進む順です。
- 3
JDK を 17 にする
Boot 3.0はJava 17以上が必須で、Java 8は対象外です。ローカルとCIの両方でJDKを揃えます。
- 4
Spring Boot 管理外の依存を洗い出す
Spring CloudのようにBootが管理しない依存は、自分でバージョンを固定しています。対応する版を先に調べないと、置換後にここでつまずきます。
テストが無いか、ほぼ通らない状態のプロジェクトでは、この記事の往復検証が機能しません。移行前に、最低限の起動確認と主要経路のテストを用意します。
javaxからjakartaへの置換はOpenRewriteに任せる
置換には、org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta のレシピがあります。Jakarta EE 9向けのレシピで、javax.activation、javax.annotation、javax.ejb、javax.elなど、パッケージごとの移行を束ねた構成です。
Spring Bootの移行は、さらに上位の org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0 が受け持ちます。このレシピは、次のような変更を連鎖して実行します。
- Java 17への移行
- Spring Framework 6.0、Spring Security 6.0、Hibernate 6.1.xへの移行
- Spring Bootのプロパティを3.0向けに改名
@EnableXXXSecurityが付いたクラスへの@Configurationの追加logback.xmlからlogback-spring.xmlへの改名
Mavenなら、pom.xml を変えずにコマンドラインから走らせる方法があります。
mvn -U org.openrewrite.maven:rewrite-maven-plugin:6.51.0:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:RELEASE \
-Drewrite.activeRecipes=org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0プラグインとレシピのバージョンは、レシピのドキュメントに載っている値を使います。ドキュメントの例では、プラグインは6.51.0です。
レシピは作業ツリーを書き換えるだけで、コミットはしません。実行の前に作業ブランチを切り、実行後は git diff で差分を見ます。差分が大きすぎるときは、パッケージごとのレシピに分けて回すと、確認しやすくなります。
Claude Codeに実行させるときの注意
レシピの実行そのものも、Claude CodeにBashで頼めます。ただし、Bashで書き換わったファイルは、Edit|Write に付けたPostToolUse hookの対象になりません。Claude Codeは、Bashコマンドや外部のプロセスが同じファイルを書き換えても、Edit|Write にマッチしたPostToolUse hookを動かさないからです。
つまり、置換の直後にテストを走らせたいなら、PostToolUseではなくStop hookで受けるのが確実です。次の節で、そのように組みます。
置換後に残る層を洗い出す
レシピを回した後に、コンパイルとテストで見えてくるのは、主に次の箇所です。
Hibernate 6
Boot 3.0はHibernate 6.1を標準にします。依存管理と spring-boot-starter-data-jpa は、org.hibernate.orm という新しいグループIDを使います。spring.jpa.hibernate.use-new-id-generator-mappings は削除されました。旧来のID生成マッピングには戻せません。
IDの採番まわりで挙動が変わったテストは、設定の問題ではなくHibernate側の変更として調べます。
Spring Security 6
Boot 3.0はSpring Security 6.0を使います。Servletアプリでは、すべてのディスパッチタイプに認可が掛かり、Bootもフィルターを全ディスパッチタイプで呼ぶように設定します。この対象は spring.security.filter.dispatcher-types で変えられます。
SAML2のRelying Party設定では、identity-provider 配下のプロパティが削除され、asserting-party 配下に置き換わりました。
WebのURLマッチング
Spring Framework 6.0で、末尾スラッシュのマッチングの既定値が false になりました。これまで /some/greeting と /some/greeting/ の両方に応答していたコントローラーは、後者に応答しなくなります。
依存の座標
MySQLのJDBCドライバーは、mysql:mysql-connector-java から com.mysql:mysql-connector-j に変わりました。javax.servlet:javax.servlet-api のようなJava EE系の依存が、直接にも推移的にも残っていないかも確認します。
これらのうち、レシピのリストに入っているもの(Spring Security 6.0への移行、Hibernate 6.1.xへの移行、プロパティの改名)は、機械側で済む可能性があります。入っていないもの、つまり独自の設定クラスや固定した外部依存は、テストが落ちて初めて見つかります。そこをClaude Codeに直させます。
Stop hookでmvn testを往復させる
Claude Codeが応答を終えようとするたびに mvn test を走らせ、失敗したら出力をClaudeに返して作業を続けさせます。Stop hookは、exit code 2でこの「止まらせない」動きを実現できます。
設定は .claude/settings.json に書きます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/mvn-test-gate.sh",
"timeout": 900,
"statusMessage": "mvn test を実行しています"
}
]
}
]
}
}timeout は秒単位で、command hookの既定は10分です。テストが長いプロジェクトでは、既定のままでも収まるか、先に測っておきます。
呼び出されるスクリプトは次のとおりです。
#!/bin/bash
# .claude/hooks/mvn-test-gate.sh
cd "$CLAUDE_PROJECT_DIR" || exit 0
OUT=$(mvn -q -B test 2>&1)
if [ $? -ne 0 ]; then
# stderr に出した内容が Claude に渡され、作業が続く
echo "mvn test が失敗しました。末尾のログです。" >&2
echo "$OUT" | tail -n 60 >&2
exit 2
fi
exit 0exit code 2のとき、Stop hookはClaudeが止まるのを防ぎ、会話を続けさせます。stderrに書いた文面が、直す手がかりとしてClaudeに届きます。テストが通ればexit 0で、Claudeはそのまま止まります。
出力は tail で絞ります。Mavenのログは長く、全文を返すとコンテキストを食うためです。
8回の上限と、自前の打ち切り
Claude Codeは、Stop hookが続けて8回ブロックすると、次のブロックを無視してターンを終えます。この連続回数は、Claudeがツールを呼ぶたびにリセットされます。上限は CLAUDE_CODE_STOP_HOOK_BLOCK_CAP で変えられます。
移行では、Claudeが修正のたびにEditやBashを呼ぶので、カウントは頻繁に戻ります。そのぶん、直せないテストがあると延々と往復します。次のように、試行回数に自前の上限を置くと安全です。
COUNT_FILE="$CLAUDE_PROJECT_DIR/.claude/.mvn-gate-count"
N=$(cat "$COUNT_FILE" 2>/dev/null || echo 0)
if [ "$N" -ge 6 ]; then
rm -f "$COUNT_FILE"
exit 0 # 6 回直せなければ人に返す
fi
echo $((N + 1)) > "$COUNT_FILE"成功したら rm -f "$COUNT_FILE" でカウントを消します。上のブロックは、mvn test を実行する前に置きます。
落とし穴
PostToolUse の mvn test は編集のたびに失敗しやすい
Edit|Write にPostToolUse hookを付けて mvn test を走らせると、1ファイルを直すたびに全体のテストが回ります。移行の途中は、まだ置換していないファイルがあってコンパイルが通らない状態が長く続きます。PostToolUseのexit 2は、すでに実行されたツールを取り消せず、stderrをClaudeに見せるだけです。往復の検証は、まとまった作業の区切りで走るStop hookに寄せます。
CLAUDE.mdに移行の方針を置く
hookは結果を返すだけで、何を直してよいかは決めません。範囲を CLAUDE.md に書いておくと、Claude Codeが余計な書き換えに走りにくくなります。
## Spring Boot 3 移行の方針
- javax.* → jakarta.* の置換は OpenRewrite で行い、手で書き換えない
- OpenRewrite の実行後は git diff を確認し、レシピが触っていない箇所だけ直す
- テストを無効化・削除して通さない。落ちたテストは原因を直す
- ライブラリのバージョンを上げるときは、Spring Boot の依存管理に合う版を確認する
- 設定プロパティの旧名が残っていないか application.yml を確認する2行目と3行目が要です。レシピの変更を上書きしないことと、テストを弱めて通さないことを明文化しておきます。
プロパティの旧名は、spring-boot-properties-migrator を一時的にruntime依存へ入れると洗い出せます。起動時に診断を出し、実行時には古いプロパティを一時的に移し替えます。移行が済んだら、依存から外します。
往復の回し方
全体は、次の順で回します。
移行の実行順
- 1
作業ブランチを切る
2.7.xの最新で
mvn testが通ることを確かめてから、移行用のブランチを切ります。 - 2
OpenRewrite を実行する
UpgradeSpringBoot_3_0を走らせ、git diffで変更の範囲を見ます。 - 3
Claude Code に残りを直させる
「
mvn testを通して」と頼みます。落ちたテストはStop hookが自動で返すので、依頼は一度で足ります。 - 4
テストが通ったら起動を確かめる
テストが通っても、実環境の設定は検証されていない場合があります。ステージングで起動し、認証とDB接続を確認します。
テストが通る状態にたどり着くまでの試行は、Claude Codeが自動で回します。人が見るのは、レシピの差分と、Claudeが直した差分の2つです。Spring Securityの設定のように挙動に関わる変更は、テストに載っていない経路があるかを自分で見直します。
依存の脆弱性まで同時に点検したいときは、Claude CodeにSonatype Guideプラグインを入れて依存関係を検査するの手順が使えます。多数のリポジトリに同じ移行を回す場合の設計は、BlockがClaude Fable 5で数千PRのコード移行を回す方法が参考になります。
移行がうまく回らないときの切り分け
hookが動かない
/hooks を開き、Stopの下に登録されているかを確認します。設定ファイルのJSONが壊れていると、hookは読まれません。
テストは通るのにClaudeが止まらない
スクリプトが標準出力に余計な文字を出していないかを確認します。シェルのプロファイルが echo を出力していて、hookの出力に混ざる例が、公式のトラブルシューティングにあります。
Stop hookが8回で打ち切られる
上限は連続8回です。収束までの試行がそれ以上要るなら CLAUDE_CODE_STOP_HOOK_BLOCK_CAP を上げるか、直せないテストを人が見ます。
レシピの差分が大きすぎて読めない
UpgradeSpringBoot_3_0 は多くの変更を連鎖させるため、1回の差分が大きくなります。Java 17、jakarta、Spring Security、Hibernateを別々のコミットに分け、1つずつ mvn test を通すと、原因の切り分けが楽になります。
まとめ
Spring Boot 3への移行で、Claude Codeに任せる範囲は、レシピの後ろです。置換そのものはOpenRewriteで一括して済ませ、残りをStop hookの mvn test と往復して潰します。
この分け方の利点は、機械に任せた部分の正しさを、差分の確認だけで担保できることです。Claude Codeが直すのは、テストが落ちた箇所に限られ、手が入った範囲を追いやすくなります。同じ型は、言語やフレームワークの別の大規模移行にも当てはまります。たとえば .NETやCOBOLの移行を扱うClaude CodeのAWS Transformプラグインも、機械変換と検証を分ける発想の延長にあります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。