2026/04/06

JEPで語るJava 26

gihyo.jpの記事や、JavaOneの参加レポート(前編後編)などを書いていたら、JEPで語るが4月になってしまいました😰

Java 26のJEPは10。そのうちの半分がStandard JEPです。LTSの次のバージョンにしては多いですね。

Java 26のJEPの一覧はこちら。

  • 500: Prepare to Make Final Mean Final
  • 504: Remove the Applet API
  • 516: Ahead-of-Time Object Caching with Any GC
  • 517: HTTP/3 for the HTTP Client API
  • 522: G1 GC: Improve Throughput by Reducing Synchronization
  • 524: PEM Encodings of Cryptographic Objects (Second Preview)
  • 525: Structured Concurrency (Sixth Preview)
  • 526: Lazy Constants (Second Preview)
  • 529: Vector API (Eleventh Incubator)
  • 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)

 

では、1つずつ紹介していきましょう。

 

JEP 504: Remove the Applet API

JEPのタイトル通り、Appletに関するAPIを削除するJEPです。

Applet Viewerなどのアプレットを動作させる方はとっくに削除されていましたが、APIはまだ残っていたのでした。

java.appletパッケージが削除されただけではなく、SwingのJAppletクラスも削除されています。

実際にはAWTやJava 2Dの内部でアプレットに関連した部分がいろいろと削除されているようです。

 

JEP 500: Prepare to Make Final Mean Final

finalをfinalにしましょうというJEPです。

「どういうこと?」と思われるかもしれませんが、今はfinalは実はfinalではないのです。

たとえば、次のプログラムを実行してみるとどうなるでしょう。

class Foo {
    final int x;

    public Foo() {
        x = 0;
    }
}

void main() throws Exception {
    var foo = new Foo();
    IO.println("Initla Value: " + foo.x);

    var clazz = Foo.class;
    var refX = clazz.getDeclaredField("x");

    // おまじない
    refX.setAccessible(true);
    
    // 値の変更
    refX.set(foo, 10);
    IO.println("Modified Value: " + foo.x);
}

リフレクションでFooクラスのxフィールドの値を書き換えているプログラムです。

このプログラムをJava 25で実行してみましょう。

C:\src> C:\Program Files\Java\jdk-25\bin\java -version
openjdk version "25" 2025-09-16
OpenJDK Runtime Environment (build 25+36-3489)
OpenJDK 64-Bit Server VM (build 25+36-3489, mixed mode, sharing)

C:\src> C:\Program Files\Java\jdk-25\bin\java Main.java
Initla Value: 0
Modified Value: 10

C:\src>

あっさりと値の書き換えができてしまいました。

ここでキーになるのはsetAccessible()メソッドです。setAccessible()メソッドの引数にtrueを指定してコールすると、finalフィールドへの代入、privateメソッドのコール、privateフィールドの柿替えなどができてしまいます。

つまり、finalフィールドであってもfinalではないという状況を作り出してしまったわけです。

 

このようにfinalフィールドの書き換えができるということをやめましょうというのが、JEP 500です。

ただし、いきなり禁止にしてしまうと、動作できなくなるアプリケーションも出てくるので、まずは警告を出すようにして、将来的には原則禁止にしていきましょうということです。

 

では、同じプログラムをJava 26で実行してみましょう。

C:\src> C:\Program Files\Java\jdk-26\bin\java -version
openjdk version "26" 2026-03-17
OpenJDK Runtime Environment (build 26+35-2893)
OpenJDK 64-Bit Server VM (build 26+35-2893, mixed mode, sharing)

C:\src> C:\Program Files\Java\jdk-26\bin\java Main.java
Initla Value: 0
WARNING: Final field x in class Main$Foo has been mutated reflectively by class Main in unnamed module @4c6e276e (file:/C:/Main.java)
WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled    
Modified Value: 10

C:\src>

値の書き換えはできるものの、Warningが出るようになりました。これがJEP 500によるものです。

 

さて、リフレクションによるfinalフィールドの値書き換えができなくなるのはいいのですが、ライブラリやフレームワークがリフレクションを使っていて警告が出てしまう場合はどうすればよいでしょう?

対応策は2種類あります。どちらも実行時オプションで指定します。

  • モジュール単位でリフレクションによるfinal変更を許可する
  • 全体の動作を指定する

1つめのモジュール単位で許可する方法は、上記のWarningの文章の中にもある--enable-final-field-mutationオプションです。

--enable-final-field-mutation=m1,m2 のようにの後に許可するモジュールをカンマ区切りで列挙します(m1とm2がモジュールです)。

すべてに許可する場合は --enable-final-field-mutation=ALL-UNNAMED とします。

 

もう一方の全体の動作を指定するには、--illegal-final-field-mutationオプションで行います。

指定できるのは、以下の4種類です。

  • allow : finalの書き換えを許可
  • warn : finalの書き換えがあると警告を出力 (デフォルト)
  • debug : finalの書き換えがあると警告とスタックトレースを出力
  • deny : finalの書き換えを禁止

Java 26でのデフォルトはwarnです。しかし、将来的にはdenyがデフォルトになる予定です。

denyがデフォルトになる前に、警告が出たら対応しておきましょう。

 

JEP 517: HTTP/3 for the HTTP Client API

java.net.httpモジュールで提供されているHTTP ClientをHTTP/3に対応させるというJEP。

HTTP/3はTCPではなく、UDPで通信するのですが、ブラウザーで使っていると違いは全然分からないですね。同じようにHTTP Clientでも、既存の使い方とまったく変わらずに通信することができます。

違いは、HTTPのバージョン指定でHTTP/3にすることだけです。

このために、HTTPのバージョンを表すHttpClient.Version列挙型に定数が追加されています。

  • HTTP_3

後は、HttpClientオブジェクトを生成するときにバージョンを指定するだけです。もしくは、リクエストごとに指定することも可能で、その場合はHttpRequestオブジェクトを生成するときにバージョンを指定します。

前者であれば、次のように記述します。

    var client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_3)
            .build();

後者のリクエストで指定するには、次のようになります。

    var request = HttpRequest.newBuilder()
            .uri(URI.create("https://google.com/"))
            .version(HttpClient.Version.HTTP_3)
            .GET()
            .build();

後は、通常の使い方と同じです。

ただし、実際にHTTP/3が使われるかどうかは、Webサーバーとのネゴシエーションによるので、HTTT/3を指定したけどHTTP/2で通信していたということもあります。

 

ただ、動作がよく分からないこともあります。たとえば、google.comに接続するときにHttpClientオブジェクトでバージョン指定してもHTTP/2が使われます。しかし、HttpRequestオブジェクトにバージョン指定するとHTTP/3で通信できます。

何かが違うのでしょうけど、イマイチよくわからず...

 

JEP 516: Ahead-of-Time Object Caching with Any GC

Standard JEPの残り2つはGC関連。ただし、JEP 516はGCというよりは、Project Leydenです。

起動時間やウォームアップ時間を短縮するために、Project LeydenではAOTキャッシュファイルを使用します。

AOTキャッシュにはロードしたクラスや、HotSpotVMのプロファイリング解析結果などを保存しておきます。

また、AOTキャッシュでは初期化したオブジェクトも保存して置くことが可能です。ここで、GCとのやり取りが出てくるわけです。

JEP 516はタイトルにAny GCとありますが、実をいうとすでにG1 GCやParallel GC、Serial GCはAOTキャッシュに対応済みでした。

しかし、ZGCは対応ができていませんでした。そこで、GCに依存しような形式でオブジェクトをキャッシュできるようにしましょうというのがJEP 516です。ただし、GCに非依存にするため、効率は若干落ちてしまいます。

このため、今まで対応できていなかったZGCやShenandoah GCなどを使用する時だけ、GC非依存フォーマットにする方がよさげです。

GCに非依存のオブジェクトキャッシュを使用する場合は、AOTキャッシュを作成する時に-XX:+AOTStreamableObjectsオプションを指定します。

 

JEP 522: G1 GC: Improve Throughput by Reducing Synchronization

G1 GCではGCのたびにオブジェクトが領域を移動し、このためオブジェクト参照先のオブジェクトのアドレスが変更されます。この参照をたどることを効率的に行うためにカードテーブルというテーブルを使用します。

このカードテーブルはGCのスレッドだけでなく、アプリケーションスレッドでも使用します。

また、G1 GCではレイテンシーを改善するために、GCスレッドとアプリケーションスレッドが並列に動作する時間が長くなります。

つまり、GCスレッドとアプリケーションスレッドの両方からアクセスされるカードテーブルは、アクセスする時に同期化が必要になるということです。

この同期化によってパフォーマンスが低下してしまうことありました。それを改善しましょうというのが、JEP 522です。

 

では、どうやって解決するかというと、カードテーブルを2つ用意して、一方を読み込み、他方を書き込み専用にしてしまおうという手法。まぁ、CopyOnWriteArrayListクラスのようなものです。

とはいうものの、従来の手法でもロック取得待ちが発生することはそれほどないはずです。よっぽどヒープが逼迫して、頻繁にGCが発生するような場合でもなければ、JEP 522の恩恵に預かることはないかもしれません。

 

Preview/Incubator JEP

ここからはお試し機能のPreview/Incubator JEPなので、簡単に触れるだけにしましょう。

いずれも、まだ変更が入る可能性があるので、その点はご注意ください。

 

JEP 524: PEM Encodings of Cryptographic Objects (Second Preview)

PEMエンコードは証明書や鍵交換で使用されるバイナリをBase64でテキスト化するエンコードです。

Java 26ではPEMRecordクラスがPEMクラスに変更されたなどがあります。

そして、次のPEMエンコードのJEPでStandard JEPになるようです。今のところ、ターゲットとなるバージョンが確定していないのですが、Java 27で正式になるでしょう。

 

JEP 525: Structured Concurrency (Sixth Preview)

6回目のプレビューでなかなかStandard JEPにならないStructured Concurrencyです。

複数のタスクをパラレルに処理させて、その結果をまとめるという用途で使用します。

Java 26ではタイムアウトを設定できるようになったり、結果をストリームで返していたのを、リストにするなどの変更がありました。

次のJEPもドラフトになっているのですが、まだプレビューのままのようです。

 

JEP 526: Lazy Constants (Second Preview)

Lazy Constantsは、Java 25ではStable ValuesだったAPIです。

Lazy Constantsについては、JJUG CCC 2025 Fallでプレゼンしましたし、解説ブログも書きましたので、そちらをご参照ください。

 

 

JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)

intやdoubleなどのプリミティブ型をパターンマッチングで使用できるようにしようというのがJEP 530です。

こちらもなかなかStandard JEPにならないですねぇ。

プリミティブ型は暗黙の型変換が絡むので、そんなこともなかなかStandard JEPにならない要因になっている気がします。

実際にJava 26でも型変換に関して厳密になるような変更が加えられています。

そして、次のJEPもまたPreview JEPになっています。ただし、次のJEPでは変更がないので、このまま行けばJava 28でStandard JEPになりそうです。

 

JEP 529: Vector API (Eleventh Incubator)

Value Classが導入されるまでIncubator JEPのままが確定しているVector APIですが、Java 26でも変更はありません。

このままずっと塩漬け状態が続くのかと思っていたのですが、内部的にはいろいろと変更があるようです。というのもJavaOneでVector APIのセッションがあったのですが、そこでどういうことを行っているかについて説明があったからです。

資料は公開されているので、参考までに。

 

まとめ

Java 26の10のJEPについて簡単に解説しました。

Standard JEPでAPIが変更されるのはJEP 517だけですが、他のJEPは安全性やパフォーマンスの強化が図られています。

だからといって、Java 26をインストールしましょうというほどではないですね。次のLTSまでの間にこのような強化が積み重なっていけばよいと思います。

次のJava 27はまだJEPが1つだけですが、ドラフトにいろいろ上がってきているのでどれがJava 27をターゲットにするのか楽しみですね。