Java 21からJava 25へ:社内初のアップデートで学んだこと
はじめに
初めまして、2025年新卒の佐藤惣一朗です。
配属された新規プロダクトを開発しているチームでは主にバックエンド開発を担当し、日々サービスの機能開発や機能改善に取り組み、チームに貢献できるように頑張っています。
配属後、私は社内で初めてプロダクトのJavaバージョンを21から25へアップデートするタスクを担当しました。
今回の記事では、その経験を踏まえて以下の内容をご紹介します。
- Java 25導入の背景
- Java 25の新機能
- 導入プロセス
- 注目すべき追加機能
Java 21からJava 25へのアップデート理由
今回Javaのバージョンを21から25へアップデートした背景には、以下の理由があります。
1. Java 21以来のLTS版であること
Java 25はJava 21以来のLTS(長期サポート)リリースであり、2033年9月までのサポートが保証されています。これにより、長期的な安定性を確保しつつ、新機能をプロダクトに取り入れることができるようになります。
2. 開発体験の向上
Java 25では、開発効率やコードの可読性を高める新機能が追加されています。これらを活用することで、開発体験の向上が期待できます。特に「JEP513:柔軟なコンストラクタ実装」により、オブジェクト生成時のコードを非常に簡潔に記述できて冗長な書き方を避けられるため、可読性と保守性が向上します。(詳細は記事の後半で紹介します。)
Java 25の新機能
まず、Java 25で正式導入された機能をご紹介します。[1]
| JEP[2] | 機能名 | 概要 |
|---|---|---|
| 503 | Remove the 32-bit x86 Port | 32ビット x86環境向けのJavaサポートの終了 |
| 506 | Scoped Values | より安全なスレッド間データ共有が可能に |
| 510 | Key Derivation Function API | 暗号鍵生成の標準API |
| 511 | Module Import Declarations | モジュール単位でのインポートが可能に |
| 512 | Compact Source Files & Instance Main Methods | クラス定義なしで main() を書ける簡素化構文 |
| 513 | Flexible Constructor Bodies | 柔軟なコンストラクタ実装が可能に |
| 514 | Ahead-of-Time Command-Line Ergonomics | AOTキャッシュ作成を簡素化 |
| 515 | Ahead-of-Time Method Profiling | AOT用にメソッド実行履歴を保存 |
| 518 | JFR Cooperative Sampling | JFRのスタックサンプリングを安定化 |
| 519 | Compact Object Headers | オブジェクトヘッダーの軽量化によるメモリ効率向上 |
| 520 | JFR Method Timing & Tracing | JFRによるメソッドの実行時間と追跡 |
導入プロセス
今回Java 25の導入は下記のようなプロセスで行いました。
1. Java 25新機能の調査
まず、Java 25で正式に追加された機能をJEPベースで調査しました。
目的は、サービス開発に活かせる機能を見つけることです。特に、開発体験の向上につながる要素に注目し、コード記述の簡素化など、開発がよりスムーズになる機能を公式ドキュメントで確認しました。
2. 削除された機能の調査
次に、Java 25で削除された・非推奨となった機能について調査しました。
目的は、バージョンアップによって非推奨機能を利用している箇所や、不整合を起こす可能性のある機能がないかを確認することです。
調査の結果、今回の主な削除は「32bit x86環境のサポート」であり、現行の開発環境やコードへの影響はありませんでした。
3. 周辺ライブラリへの影響調査 & 実装
Java 25へのアップグレードに伴い、依存しているライブラリやフレームワークに影響が出る可能性があるため、以下の観点で調査を実施しました。
- 使用中のライブラリの互換性(例:Spring Boot、Jackson、JUnitなど)
- CI/CD環境におけるJavaバージョン切り替えに伴う設定変更
これらの調査を進めるとともに、実際にアップグレード作業を行い、実行時にエラーが発生しないか、問題なく動作するかを確認しました。
アップデート時に発生した警告・エラー
Java 21からJava 25へのアップグレードでは、いくつかの警告やエラーに直面しました。ここでは、その事例と対処法についてご紹介します。
アプリケーション起動時に警告
Java 25にバージョンアップすると、サーバ起動時に下記の警告が発生するようになりました。
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::allocateMemory has been called by io.netty.util.internal.PlatformDependent0$2 (file:/Users/your/.gradle/caches/modules-2/files-2.1/io.netty/netty-common/4.1.127.Final/ada4ab671678f956e1cd5067ba94bc340af1d8bf/netty-common-4.1.127.Final.jar)
WARNING: Please consider reporting this to the maintainers of class io.netty.util.internal.PlatformDependent0$2
WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release
この警告は、Java 23以降で非推奨となったsun.misc.Unsafeクラスを利用した場合に、Java 24以降で表示されるものです。
実際にはアプリケーションコードで直接使用しているわけではなく、software.amazon.awssdkライブラリが依存するNetty内部で利用されています。
対応
- 非推奨機能ではあるものの、Netty内部での利用であり、既存機能への影響はないと判断。
- 今回は警告のみで動作に問題がないため、対応は見送りました(将来的にはNettyの更新で解消される見込み)。
CI/CD実行時にエラー
PRをpushしてCI/CDでビルドを実行した際、以下のエラーが発生しました。
* What went wrong:
Execution failed for task ':ooo:jibDockerBuild'.
> Check the full stack trace, and if the root cause is from ASM ClassReader about unsupported class file version, see https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#i-am-seeing-unsupported-class-file-major-version-when-building
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to generate a Build Scan (Powered by Develocity).
> Get more help at https://help.gradle.org.
原因は、Jibプラグインが内部で使用するASM ClassReaderがJava 25に未対応だったことです。
Javaのクラスファイルバージョンが新しくなったことで、ASMが読み込めずエラーになっていました。
対応
- 当時(2025年10月時点)、Jib側が対応中だったため、Jib公式の回避策を採用。
- build.gradleにmainClassを明示的に設定することでエラーを回避しました。
container {
mainClass = 'mypackage.MyApp'
}
注目の機能
今回、Java 25の導入によりさまざまな機能が追加されました。
その中でも、実際に開発で触れてみて「これは便利!」と感じた機能をピックアップしてご紹介します。
JEP513 柔軟なコンストラクタ実装
Javaでは従来、コンストラクタから別のコンストラクタを呼び出す場合、最初の文は super(...) または this(...) でなければならず、呼び出し前に任意の処理を記述することはできませんでした。
そのため、例えば引数のバリデーションやログ出力をしたい場合、別メソッドを呼び出すなど、少し面倒な対応が必要でした。
public class Child extends Parent {
private final String normalizedName;
public Child(String name) {
super(normalize(name)); // 最初に必ず super() または this()
this.normalizedName = normalize(name); // 再度変換が必要
}
private static String normalize(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name required");
}
return name.trim().toUpperCase();
}
}
Java 25での改善
Java 25では、コンストラクタ内でsuper(...)やthis(...)の前に任意の処理を記述できるようになりました。
これにより、バリデーションやログ出力、条件付き初期化などが簡単に記述できます。
public class Child extends Parent {
private final String normalizedName;
public Child(String name) {
// super() の前に検証・変換が可能
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name required");
}
String norm = name.trim().toUpperCase();
this.normalizedName = norm; // フィールド代入OK
super(norm); // 親コンストラクタ呼び出し
}
}
まとめ
今回は、Java 25導入時のプロセスや注目機能についてご紹介しました。
Java 25リリースからわずか一ヶ月弱でアップデート対応を行ったため、ライブラリの対応が追いついていないケースや、ドキュメントをしっかり読み込まないと理解できない部分が多く、非常に苦戦しました。
しかし、この調査過程はJavaの進化やJEPの背景を深く理解する良い機会となりました。
今後もJavaの新機能を積極的に取り入れ、より効率的で安全な開発環境を構築していきたいと考えています。
Discussion