筆者はこれまで複数の案件で Java / Spring Boot のバージョンアップを担当してきました。Java 11 → 17 → 21、Spring Boot 2.x → 3.x → 3.4 と段階的にアップグレードしてきた中で、繰り返し遭遇する問題や、事前に知っておけばスムーズに進められるポイントが見えてきました。
本記事では、それらの経験を案件横断的に整理し、Spring Boot のバージョンアップに取り組むエンジニアの参考になる実践ガイドとしてまとめます。
対象読者
- Java / Spring Boot で開発されたプロジェクトのバージョンアップをこれから実施する、または検討しているエンジニア
- 特に Spring Boot 2.x → 3.x の大型マイグレーションに不安がある方
バージョンアップの進め方
大前提:Java バージョンの確認
Spring Boot 3.x は Java 17 以上が必須です(Spring Boot 2.x は Java 8 以上)。Java 8 や 11 を使用している場合は、Spring Boot のバージョンアップに先立って Java 17 以上にアップグレードする必要があります。筆者の案件でも先に Java をバージョンアップしてから Spring Boot のバージョンアップに着手しました。
なお、IntelliJ IDEA を使用している場合は、JDK のインストールだけでなく、プロジェクト構造の設定(ファイル → プロジェクト構造 → プロジェクト SDK)も新しい Java バージョンに変更する必要があります。これを忘れると、Java のバージョンが合わないというエラーメッセージが大量に出力されます。
大前提:テストコードを充実させる
バージョンアップ作業に着手する前に、テストコードが十分なカバレッジで整備されていることを確認してください。
本記事で推奨する「段階的なバージョンアップ」では、マイナーバージョンを1つ上げるたびにテストを実行してデグレードがないことを確認します。テストコードが不十分だと、各ステップで手動テストを実施することになり、テストだけで大きなコストがかかってしまいます。十分なカバレッジがない場合は、バージョンアップの前にテストコードの実装を優先することをお勧めします。
段階的にバージョンを上げる
Spring Boot のバージョンアップでは、一気に最新バージョンまで上げるのではなく、マイナーバージョン単位で段階的に上げていくことを強くお勧めします。
実は、筆者が最初にバージョンアップ作業を担当した案件では、Spring Boot 2.3 からいきなり当時の最新である 3.1 にバージョンアップし、発生したエラーを1つずつ対処していくという進め方をしていました。
- 2.3.x → 3.1.x(一気にメジャーバージョンアップ)
- 3.1.x → 3.2.x
- 3.2.x → 3.3.x
- 3.3.x → 3.4.x
この進め方だと、発生したエラーがどのバージョンのどの変更に起因するものなのか調査に時間がかかりました。また、事前に OpenRewrite で対応しているレシピを調べておけば手動で修正する工数も削減できたはずです。
その反省を踏まえ、以降の案件ではマイナーバージョンを1つずつ上げる方式に切り替えました。実際に 3.1 → 3.2 のステップではエラーが発生しましたが、3.2 → 3.3 ではエラーが一切発生しなかったケースもあり、各ステップでのエラー原因の特定が格段にしやすくなりました。
Release Notes を事前に調査する
各バージョンの Release Notes には、破壊的変更や非推奨化された API が記載されています。バージョンアップ作業に着手する前に、対象バージョン間の Release Notes をすべて確認しておくことで、影響範囲を事前に把握できます。Release Notes は Spring Boot の GitHub Wiki にまとめられています。また、メジャーバージョンアップ(2.x → 3.x 等)については Migration Guide も用意されているので、あわせて参照してください。
例えば、3.3 → 3.4 のアップグレードでは、Release Notes を事前に確認したことで @MockBean の非推奨化や ClientHttpRequestFactory 周りの変更を事前に把握でき、スムーズに対応できました。
ただし、Release Notes にすべての変更点が記載されているわけではありません。筆者の経験でも、RestTemplateBuilder の setConnectTimeout の非推奨化が Release Notes の非推奨一覧に記載されていなかったケースがありました。Release Notes の事前調査で影響範囲を大幅に絞り込むことはできますが、最終的にはテストを実行して初めて気づく変更点もあるという前提で臨んでください。
OpenRewrite による自動修正の活用
OpenRewrite は、コードの自動リファクタリングツールです。Spring Boot のバージョンアップ用のレシピが用意されており、機械的な修正を自動化できます。
筆者がある案件で Spring Boot 2.3 → 3.5 のアップグレードに OpenRewrite を利用したところ、以下のような修正が自動的に行われました。
javax.*→jakarta.*のパッケージ名変更- MySQL JDBC ドライバの
mysql:mysql-connector-java→com.mysql:mysql-connector-jへの変更 @MockBean→@MockitoBeanへの移行HandlerInterceptorAdapterのextends→HandlerInterceptorのimplementsへの変更application.ymlのプロパティ名変更(spring.datasource.initialization-mode→spring.sql.init.mode)
上記は筆者が実際に自動修正された項目ですが、これ以外にも Spring Security の設定変更(WebSecurityConfigurerAdapter → SecurityFilterChain、Lambda DSL 移行等)や Apache HttpClient 4 → 5 の移行など、本記事で紹介する多くの修正に対応するレシピが用意されています。
現在は AI を活用して同様の機械的な修正を行うこともできますが、OpenRewrite はフレームワークの移行ルールに基づいて正確に修正を行うため、修正漏れや誤変換のリスクが低く安心感があります。ただし、プロジェクト固有の実装やカスタマイズ部分は手作業での対応が必要になるため、自動修正後の確認は必須です。
IntelliJ IDEA 2024.1 Ultimate 以降では OpenRewrite プラグインが利用可能で、ビルドファイル(pom.xml / build.gradle)を開くとエディタ上にアイコンが表示され、そこからレシピを選択して実行できます。コマンドラインで実行するよりも手軽に利用できるのでお勧めです。
注意: IntelliJ IDEA 2026.1 では Java プラグインのモジュール分割等の大規模な破壊的変更が複数同時に入った影響で、2026年4月時点では OpenRewrite プラグインが未対応となっています。
ビルドツールのバージョンアップ
Spring Boot のバージョンアップに伴い、Gradle や Maven もバージョンアップが必要です。
Spring Initializr で対象の Spring Boot バージョンを指定してテンプレートプロジェクトを生成し、そのプロジェクトに含まれるビルドツールの設定を参照するのが確実な方法です。Gradle の場合は gradle/ フォルダをコピーし、Maven の場合は maven-wrapper.properties のバージョンを合わせます。筆者はこの方法で Gradle 6.x → 8.x、Maven 3.6 → 3.9 へのバージョンアップを複数案件で実施しました。
また、Gradle のメジャーバージョンアップでは構文の変更にも対応が必要です。Gradle 7 で compile が非推奨となり、Gradle 8 で完全に削除されたため、implementation に書き換える必要があります。
// Before
compile 'org.springframework.boot:spring-boot-starter-web'
// After
implementation 'org.springframework.boot:spring-boot-starter-web'
同様に、CheckStyle や Jacoco の reports ブロックで .enabled <boolean> が廃止され、.required = <boolean> に変更する必要があります。
// Before
reports {
xml.enabled true
html.enabled false
}
// After
reports {
xml.required = true
html.required = false
}
Spring Boot 2.x → 3.x の変更点
Spring Boot 2.x → 3.x は、Java EE → Jakarta EE の移行を含む大きな変更です。まずは公式の Migration Guide に記載されている変更点のうち筆者が実際に対処が必要だったものを紹介し、その後に Migration Guide には記載されていないものの遭遇した変更点を紹介します。
公式 Migration Guide に記載されている変更
以下は Spring Boot 3.0 Migration Guide や Spring Security 6.0 Migration Guide に記載されている変更点のうち、筆者が実際に対処が必要だったものです。
Jakarta EE への移行(javax → jakarta)
Spring Boot 3.0 で Java EE から Jakarta EE に移行したことにより、パッケージ名が javax.* から jakarta.* に変更されました。これはほぼすべてのプロジェクトで影響が発生します。
// Before
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
// After
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotNull;
影響範囲が広いものの、修正自体は機械的なので、IDE の一括置換や OpenRewrite を利用すると効率的です。
MySQL JDBC ドライバの変更
Spring Boot 3.0 で MySQL JDBC ドライバの座標が mysql:mysql-connector-java から com.mysql:mysql-connector-j に変更されました。
あわせて確認しておきたいのが、接続 URL のタイムゾーン指定です。serverTimezone=JST のようなタイムゾーン略称は、公式ドキュメントで非推奨とされています。現時点では内部マッピングにより JST は Asia/Tokyo に解決されますが、将来のバージョンでマッピングが削除される可能性もあるため、serverTimezone=Asia/Tokyo のように IANA タイムゾーン名で指定しておくことをお勧めします。
Spring Security の設定方法の変更
WebSecurityConfigurerAdapter が削除されたため、SecurityFilterChain を @Bean として登録する方式に移行する必要があります。筆者が担当したすべての案件でこの対応が必要でした。
// Before
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated();
}
}
// After
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
主な変更点は以下の通りです。
| Before | After |
|---|---|
extends WebSecurityConfigurerAdapter | 削除(POJO クラスに) |
configure(HttpSecurity http) のオーバーライド | SecurityFilterChain を @Bean 登録 |
authorizeRequests() | authorizeHttpRequests() |
antMatchers() | requestMatchers() |
and() によるメソッドチェーン | Lambda DSL(引数にラムダ式を渡す方式) |
@EnableGlobalMethodSecurity | @EnableMethodSecurity(prePostEnabled はデフォルトで true のため、明示的な指定は不要) |
Apache HttpClient 4 → 5 への移行
Spring Framework 6.0 以降では Apache HttpComponents 5.1 以降が必須となりました。build.gradle に依存関係を追加した上で、import 文やクラス名を変更する必要があります。
// build.gradle に追加
implementation 'org.apache.httpcomponents.client5:httpclient5'
パッケージ名が org.apache.http.* から org.apache.hc.client5.http.* に変わりますが、クラス名やメソッド名も変更されているため、単純な文字列置換では対応できません。OpenRewrite には Apache HttpClient 5 への移行用の充実したレシピが用意されているので、利用できる場合は活用すると良いでしょう。手作業で移行する場合は 公式の Migration Guide を参照してください。
なお、RestTemplate は既にメンテナンスモードになっており、Spring Boot 3.2 以降では RestClient への移行が推奨されています。Apache HttpClient を直接利用している箇所が多い場合は、バージョンアップのタイミングで RestClient への移行も検討すると良いでしょう。
URL マッチングのデフォルト変更
Spring Boot 3.0 では URL マッチングに関して2つの大きな変更がありました。
1. デフォルトの URL マッチングクラスが AntPathMatcher から PathPattern に変更
PathPattern では AntPathMatcher で許容されていた一部のパターンが使えなくなります。例えば、/link/{.*} のようなパターンは PathPattern では構文エラーとなります。
移行先はコントローラでキャプチャ変数を使用しているかどうかで異なります。
- キャプチャ変数を使用していない場合 →
/link/**で OK @PathVariableでキャプチャした値を使用している場合 →/link/{*path}が正しい移行先
なお、{*path} は AntPathMatcher の {path:.*} と異なり、キャプチャされる値の先頭に / が付く点に注意が必要です。
2. URL 末尾のスラッシュを無視しないようデフォルトの挙動が変更
これまで /api/users と /api/users/ は同じエンドポイントとしてマッチしていましたが、Spring Boot 3.0 からはデフォルトでは別のパスとして扱われます。
この変更は段階的に進んでおり、対応方法も変遷しています。
| バージョン | 状況 |
|---|---|
| Spring Boot 3.0 (Spring Framework 6.0) | デフォルトで末尾スラッシュを区別するよう変更。PathMatchConfigurer.setUseTrailingSlashMatch(true) で元の挙動に戻せるが、同時にこのメソッドが非推奨化 |
| Spring Boot 3.4 (Spring Framework 6.2) | 代替手段として UrlHandlerFilter が導入。末尾スラッシュありの URL をスラッシュなしの URL にリダイレクトする仕組み |
| Spring Boot 4.0 (Spring Framework 7.0) | setUseTrailingSlashMatch メソッドが完全に削除 |
最終的に UrlHandlerFilter に移行する必要がありますが、これはパス単位でリダイレクトルールを設定する必要があるため、すべてのパスを末尾スラッシュなしに統一しておく必要があります。詳細は Spring Framework のドキュメント を参照してください。
筆者が実際に遭遇したその他の変更
以下は公式の Migration Guide には記載されていないものの、筆者が実際の案件で遭遇した変更点です。
ErrorController の getErrorPath() メソッド削除
ErrorController インターフェイスの getErrorPath() メソッドは Spring Boot 2.3 で非推奨となり、2.5 で削除されました。2.x の段階で既に削除されていますが、筆者のように 2.3 から一気に 3.x にバージョンアップした場合はこの変更に遭遇します。
代替として server.error.path プロパティで設定する方式に移行されています。getErrorPath() の戻り値が "/error"(デフォルト値と同じ)であった場合は、単にメソッドを削除するだけで対応できます。
HandlerInterceptorAdapter の廃止
HandlerInterceptorAdapter 抽象クラスが削除されたため、HandlerInterceptor インターフェイスを直接 implements するよう変更します。
// Before
public class MyInterceptor extends HandlerInterceptorAdapter {
// After
public class MyInterceptor implements HandlerInterceptor {
メソッドのシグネチャは変わらないため、修正は extends → implements への変更だけで済みます。
HttpStatus → HttpStatusCode
Spring Framework 6 から、一部の API でメソッドの引数や戻り値の型が HttpStatus(enum)から HttpStatusCode(インターフェイス)に変更されました。
// Before
public ResponseEntity<ErrorResponse> handleException(HttpStatus status) {
// After
public ResponseEntity<ErrorResponse> handleException(HttpStatusCode status) {
Bean 循環参照のデフォルト禁止
Spring Boot 2.6 から Bean の循環参照がデフォルトで禁止されました。2.x 時代にこの設定を有効化していなかった場合、3.x へのアップグレード時にエラーが発生します。spring.main.allow-circular-references=true で一時的に回避できますが、根本的には循環参照を解消すべきです。
@Async メソッドの戻り値制約の厳格化
Spring Framework 6.0 から、@Async を付与したメソッドの戻り値は void か Future でなければならないという制約が厳格に適用されるようになりました。これまで暗黙的に許容されていた他の戻り値型を使っていた場合、実行時にエラーが発生します。
Thymeleaf のフラグメント式の非推奨化
Spring Boot 3.0 に含まれる Thymeleaf 3.1 から、~{} で囲わないフラグメント式が非推奨となりました。単体テストでは検出されず、ステージング環境での手動テストで初めて大量の WARN ログとして発覚するケースがありました。
<!-- Before -->
<div th:replace="fragments/header :: header"></div>
<!-- After -->
<div th:replace="~{fragments/header :: header}"></div>
詳細は Thymeleaf 3.1: What's new and how to migrate の 2.6 を参照してください。
application.yml のプロパティ名変更
Spring Boot 2.5 からデータソース初期化に関するプロパティ名が変更されています。2.x 時代は非推奨の警告のみでしたが、3.x では旧プロパティ名が認識されなくなるため、変更が必須です。
# Before
spring.datasource.initialization-mode: always
# After
spring.sql.init.mode: always
この他にも複数のプロパティ名が変更されています。詳細は Spring Boot 2.5 Release Notes を参照してください。
Spring Boot 3.x 内のマイナーバージョンアップで注意すべき変更
2.x → 3.x のメジャーバージョンアップに比べると変更は小さいですが、マイナーバージョンアップでも注意が必要な変更があります。
Spring Boot 3.2: NoResourceFoundException の導入
Spring Framework 6.1(Spring Boot 3.2)から、静的リソースが見つからない場合に ResourceHttpRequestHandler が NoResourceFoundException を throw するようになりました。従来 ErrorController でカスタマイズしていた 404 レスポンスが正しく返らなくなるケースがあります。この場合は ResponseEntityExceptionHandler の handleNoResourceFoundException メソッドをオーバーライドして対応します。
Spring Boot 3.2: SimpleClientHttpRequestFactory のリクエストバッファリング非推奨化
同じく Spring Framework 6.1(Spring Boot 3.2)から、SimpleClientHttpRequestFactory の setBufferRequestBody メソッドが非推奨かつ削除予定となりました。それだけでなく、メソッドの実装自体が空になっており、設定しても効果がありません。
setBufferRequestBody(false) でバッファリングを無効化していた場合は、そもそも設定が不要になったため、呼び出し箇所を単に削除するだけで対応できます。一方、setBufferRequestBody(true) でバッファリングを有効化していた場合は、使用している ClientHttpRequestFactory を BufferingClientHttpRequestFactory でラップする方法が 公式に推奨 されています。
ClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
ClientHttpRequestFactory bufferingFactory = new BufferingClientHttpRequestFactory(factory);
RestTemplate restTemplate = new RestTemplate(bufferingFactory);
Spring Boot 3.3: Flyway 10 のモジュール分割
Spring Boot 3.3 で Flyway 10 に対応したことにより、データベース固有の機能が個別のモジュールに分離されました。使用しているデータベースに応じて、対応するモジュールを依存関係に追加する必要があります。
<!-- PostgreSQL の場合 -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
Spring Boot 3.4: @MockBean / @SpyBean の非推奨化
Spring Boot 3.4 で @MockBean と @SpyBean が非推奨となり、代わりに @MockitoBean と @MockitoSpyBean を使うよう変更が必要です。
// Before
@MockBean
private UserService userService;
// After
@MockitoBean
private UserService userService;
アノテーション名の置換だけで対応できます。
Spring Boot 3.4: @ConfigurationProperties のネストクラスに @Valid が必要に
Spring Boot 3.4 から、@ConfigurationProperties を付与したクラス内のネストしたクラスに Bean Validation のアノテーション(@NotNull 等)が使用されている場合、そのネストクラスに対応するフィールドに @Valid を付与する必要があります。
@ConfigurationProperties(prefix = "app")
public record AppProperties(
@Valid Notification notification // 3.4 から @Valid が必要
) {
public record Notification(
@NotNull String endpoint,
@NotNull Integer retryCount
) {}
}
@Valid が付与されていないと、ネストクラス内のバリデーションが実行されなくなります。
Spring Boot 3.4: RestTemplateBuilder / ClientHttpRequestFactory の変更
Spring Boot 3.4 では、RestTemplateBuilder の setConnectTimeout メソッドが非推奨となり、新設の connectTimeout メソッドを使用するよう変更されました。
// Before
restTemplateBuilder.setConnectTimeout(Duration.ofSeconds(5))
// After
restTemplateBuilder.connectTimeout(Duration.ofSeconds(5))
なお、この変更は Release Notes の非推奨一覧には記載されておらず、ClientHttpRequestFactory Builders の項目に関連して行われた変更でした。Release Notes だけでなく、実際の JavaDoc も確認することをお勧めします。
また、ClientHttpRequestFactorySettings.DEFAULTS も非推奨となり、ClientHttpRequestFactoryBuilder への移行が必要です。ここで移行先の選択を間違えやすいので注意が必要です。
ClientHttpRequestFactoryBuilder には simple()、jdk()、httpComponents() 等のファクトリメソッドがありますが、これらは全く異なる HTTP クライアント実装に対応しています。
| メソッド | HTTP クライアント実装 |
|---|---|
simple() | java.net.HttpURLConnection(Java 1.1 からある旧 API) |
jdk() | java.net.http.HttpClient(Java 11 で導入された新 API、HTTP/2 対応) |
httpComponents() | Apache HttpClient 5 |
ClientHttpRequestFactorySettings.DEFAULTS は、クラスパス上のライブラリを検出して以下の優先順位でファクトリを選択していました。
- Apache HttpComponents がクラスパスにあれば →
HttpComponentsClientHttpRequestFactory - Jetty がクラスパスにあれば →
JettyClientHttpRequestFactory - Reactor Netty がクラスパスにあれば →
ReactorClientHttpRequestFactory - JDK HttpClient(Java 11+)→
JdkClientHttpRequestFactory - いずれもなければ →
SimpleClientHttpRequestFactory(HttpURLConnectionベース)
したがって、DEFAULTS からの移行先は、クラスパスの状態に応じて適切なメソッドを選ぶ必要があります。特に Apache HttpComponents も Jetty も使用していないプロジェクトでは simple() が正しい移行先であり、jdk() ではありません。
筆者が担当した案件では DEFAULTS を jdk() に移行したところ、外部 SaaS への HTTP リクエストが 2 回に 1 回タイムアウトするという問題が発生しました。java.net.http.HttpClient はデフォルトで HTTP/2 のネゴシエーションを試みるため、相手サーバーとの間で接続確立に問題が起きていたものと考えられます。simple() に変更したところ問題は解消しました。
筆者が経験したライブラリ移行
Spring Boot のバージョンアップは、依存ライブラリの移行が必要になる良い機会でもあります。以下は筆者が実際の案件で対応したライブラリ移行です。
springfox → springdoc-openapi
springfox は 3.0.0(2020年リリース)を最後に長期間更新されておらず、Spring Boot 3.x では動作しません。@EnableSwagger2 を残したままにすると javax.servlet.http.HttpServletRequest が見つからないというエラーが発生します。
代替として springdoc-openapi への移行が推奨されています。
// Before
implementation 'io.springfox:springfox-boot-starter:3.0.0'
// After
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.4'
Swagger の設定クラスがカスタマイズされていない場合は、設定クラスごと削除して springdoc-openapi のデフォルト設定を使うことで移行コストを下げられます。
AWS SDK v1 → v2
AWS SDK for Java v1 は 2025年12月31日で End of Support を迎えました。v2 への移行が必須ですが、API の設計が v1 とは大きく異なるため(特に DynamoDB)、影響範囲が大きくなりがちです。
DynamoDB を利用している場合は、AWS SDK v2 をそのまま使うよりも Spring Cloud AWS の利用を検討すると良いでしょう。DynamoDbTemplate が利用可能になる等の利便性向上に加え、テーブル名 prefix 対応などの実用的な機能も提供されています。
GSON → Jackson
Spring Boot には Jackson が標準で含まれているため、GSON と併用する意味はほとんどありません。さらに、Java 17 以降の JDK 内部要素のカプセル化(JEP 403)により、GSON がリフレクションで内部クラスにアクセスして InaccessibleObjectException が発生するケースがあります。
java.lang.reflect.InaccessibleObjectException: Unable to make field
private final int java.time.LocalDate.year accessible: module java.base
does not "opens java.time" to unnamed module
このエラーが発生した場合は、GSON を Jackson に置き換えることで解決できます。バージョンアップを機に、JSON ライブラリを Jackson に統一することをお勧めします。
jjwt のメジャーバージョンアップ
jjwt は 0.9.x → 0.12.x でメソッド名や依存ライブラリの構成が大きく変わっています。まとまったマイグレーションガイドが公式には存在しないため、以下に主要な変更点を記載します。
依存関係の変更
// Before
implementation 'io.jsonwebtoken:jjwt:0.9.1'
// After
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
トークン作成の変更
// Before
String token = Jwts.builder()
.setIssuedAt(issuedAt)
.setExpiration(expiration)
.setSubject(subject)
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
// After
String token = Jwts.builder()
.issuedAt(issuedAt)
.expiration(expiration)
.subject(subject)
.signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)), Jwts.SIG.HS512)
.compact();
トークン検証の変更
// Before
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
// After
Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)))
.build()
.parseSignedClaims(token);
また、io.jsonwebtoken.SignatureException が非推奨となり、io.jsonwebtoken.security.SignatureException に移動しています(後者は前者のサブクラスなので、import 文の変更だけで対応可能)。
更新が停止しているライブラリの対処
バージョンアップは、長期間更新されていないライブラリを見直す良い機会です。筆者が遭遇した例を紹介します。
- jBCrypt(最終更新 2020年)→
spring-security-cryptoに含まれるBCryptPasswordEncoderに移行。jBCrypt が必要なプロジェクトでは Spring Security も使用している可能性が高いため大抵の場合spring-boot-starter-securityに含まれる形で利用できるが、筆者が担当したあるサーバーでは例外的に Spring Security を使用していなかった。その場合、spring-boot-starter-securityを追加すると AutoConfiguration により認証・認可の挙動が変化してしまうため、暗号化機能だけを利用するためにspring-security-cryptoのみを依存関係に追加する必要があった - javafaker(最終更新 2020年2月)→ 調査の結果、コメントアウトされたコードでしか使われていなかったため削除
また、Spring Boot が管理しているライブラリ(Lombok、Jackson、Spring Security Test 等)に個別にバージョンを指定している場合は、バージョン指定を削除して Spring Boot の依存関係管理に任せるようにしましょう。
Java バージョンアップに伴う注意点
Spring Boot のバージョンアップと同時に Java のバージョンも上げる場合、Java 側の変更に起因するエラーにも対応が必要です。
JDK 内部要素のカプセル化(JEP 403, Java 17〜)
Java 17 から JEP 403: Strongly Encapsulate JDK Internals により、JDK 内部の API へのリフレクションによるアクセスがデフォルトで禁止されました。
筆者が遭遇したケースでは、Apache Commons Lang の ReflectionToStringBuilder.toString() メソッドが以下のエラーを発生させました。
java.lang.reflect.InaccessibleObjectException: Unable to make field
static final int java.util.HashMap.DEFAULT_INITIAL_CAPACITY accessible:
module java.base does not "opens java.util" to unnamed module
--add-opens JVM オプションで回避することも可能ですが、根本的にはリフレクションに依存しないコードに修正することが望ましいです。実際に上記のケースでは ReflectionToStringBuilder の出力内容自体がデバッグに有用でない内容(java.util.HashMap@535f6e95 のような文字列)だったため、効果的なログが出力されるよう実装を見直しました。
Mockito の動的エージェントロード警告(Java 21〜)
Java 21 以降では、Mockito が動的にエージェントをロードする際に以下のような警告が出力されます。
WARNING: A Java agent has been loaded dynamically
WARNING: Dynamic loading of agents will be disallowed by default in a future release
Maven の場合は、maven-surefire-plugin の設定で Mockito をエージェントとして指定します。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar}</argLine>
</configuration>
</plugin>
Gradle の場合は、test タスクの jvmArgs に設定します。詳細は Mockito の公式ドキュメント を参照してください。
バージョンアップ時に役立つ実践的な Tips
不要な依存関係の棚卸し
バージョンアップは依存関係を見直す良い機会です。筆者が複数案件で実施した棚卸しでは、以下のようなケースが見つかりました。
- 使用されていないライブラリ:build.gradle / pom.xml に記載されているが、実際のコードで使われていないもの。削除してテストが通れば不要
- Spring Boot が管理しているライブラリの個別バージョン指定:
spring-boot-starter-*に含まれるライブラリのバージョンを個別に固定している場合、その指定を削除して Spring Boot の BOM に任せる - 過去の残骸:別のライブラリに移行済みなのに旧ライブラリが残っているケース
Error Prone の新チェックへの対応
Error Prone を利用している場合、バージョンアップに伴い新しいチェックが有効化されることがあります。筆者が遭遇した例と、その際にとった対応を以下に記載しますが、@SuppressWarnings による抑制はあくまで暫定的な対応であり、可能であればチェックの指摘に従ってコードを改善する方が望ましいです。
| チェック | 内容 | 筆者がとった対応 |
|---|---|---|
NullablePrimitive | プリミティブ型に @NotNull が付与されている | 無意味な @NotNull を削除 |
EnumOrdinal | ordinal() の使用を検出 | 既存の動作への影響を考慮し @SuppressWarnings で抑制 |
StatementSwitchToExpressionSwitch | switch 文を switch 式に変換可能 | switch 式に書き換え |
IntLiteralCast | int リテラルを long にキャスト | リテラルに L サフィックスを付与 |
UnicodeInCode | コード中に非 ASCII 文字 | テストメソッド名の日本語に対して @SuppressWarnings で抑制 |
EnumOrdinal や UnicodeInCode については @SuppressWarnings で対応しましたが、本来は ordinal() を使わない実装への修正や、テストメソッド名の命名規則の見直しが理想的です。バージョンアップのスコープとの兼ね合いで判断してください。
commons-logging の競合
AWS SDK v1 や Firebase Admin SDK など、commons-logging を推移的に依存として含むライブラリがクラスパスにあると、Spring Boot 3.x の spring-jcl と競合して起動に失敗することがあります。
Standard Commons Logging discovery in action with spring-jcl:
please remove commons-logging.jar from classpath in order to avoid potential conflicts
該当のライブラリから commons-logging を除外することで解決します。
// Gradle
implementation('com.amazonaws:aws-java-sdk') {
exclude group: 'commons-logging', module: 'commons-logging'
}
なお、AWS SDK v2 には commons-logging が含まれないため、v2 に移行すればこの問題は自然に解消されます。
まとめ
本記事では、筆者が複数案件で Java / Spring Boot のバージョンアップを実施した経験から得た知見を整理しました。ポイントを振り返ります。
- テストコードを充実させてからバージョンアップに着手する。段階的なバージョンアップの各ステップで自動テストにより確認できる体制が大前提
- 段階的にバージョンを上げることで、エラーの原因特定が容易になる
- Release Notes の事前調査が最も効率的な準備。実際に影響がある変更を事前に把握できる
- OpenRewrite を活用して、
javax→jakarta等の機械的な修正を自動化する - バージョンアップは依存関係の棚卸しの好機。使われていないライブラリの削除や、更新が停止したライブラリの代替への移行を実施する
Spring Boot 2.x → 3.x はJakarta EE 移行を含む大きな変更ですが、段階的に進めれば一つ一つの対応は難しくありません。本記事が、これからバージョンアップに取り組む方の参考になれば幸いです。
続編について
現在、Java 17/21 → 25 および Spring Boot 3.x → 4.x へのアップデートにも取り組んでいます。Spring Boot 4.0 は Spring Framework 7 ベースであり、本記事で触れた setUseTrailingSlashMatch メソッドの完全削除をはじめ、さらに大きな変更が入っています。その知見も別の記事にまとめる予定です。



