🦔

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の新機能を積極的に取り入れ、より効率的で安全な開発環境を構築していきたいと考えています。

脚注
  1. プレビュー機能やインキュベーター機能などもありますが、かなり多くなってしまうため今回は割愛させていただきます ↩︎

  2. JEPはJava Enhancement Proposalの略で、Java言語やJVM、標準ライブラリに対する改善提案をまとめたドキュメントのこと ↩︎

WealthNavi Engineering Blog

Discussion