Java 26が2026/3/17にリリースされました。
Java 26 / JDK 26: General Availability - jdk-dev - openjdk.org
Oracle Releases Java 26
The Arrival of Java 26
※ メーリングリストのシステムが変わってますね。
非LTSで、前回LTSのJava 25直後ともあって、Java 26ではあまり目立った変更はありません。
今回、直接影響がありそうな変更はHTTP Client APIでのHTTP/3対応くらいです。
JDKをインストールせずに言語やライブラリの新機能を試したい場合にはJava Playgroundが便利です。
https://dev.java/playground/
イベント
東京でのJJUGのイベントは公開遅れてる間に終了しました・・・
【オフライン】JJUGナイトセミナー「Java 26 リリース記念イベント」3/24(火) 開催 - 日本Javaユーザーグループ/Japan Java User Group | Doorkeeper
福岡では4/9にLINEヤフー博多オフィスでLT会とあわせて行います。(終了しました)
Java 26とLT会 at 福岡 - connpass
資料
詳細はこちら
JDK 26 Release Notes
Java SE 26 Platform JSR 401
OpenJDK JDK 26 GA Release
APIドキュメントはこちら
Overview (Java SE 26 & JDK 26)
追加されたAPIまとめはこちら
https://docs.oracle.com/en/java/javase/26/docs/api/new-list.html
ディストリビューション
MacやLinuxでのインストールにはSDKMAN!をお勧めします。
Oracle OpenJDK以外に無償で商用利用できるディストリビューションとしては、次のようなものがあります。
※ Microsoft Build of OpenJDK、GraalVMは非LTSバージョンのリリースはありません
どのディストリビューションを使えばいいかわからないときは、とりあえずEclipse Temurinが無難です。
アップデートは4月に26.0.1が、7月に26.0.2がリリースされることになります。
Oracle JDKの無償ライセンスであるNFTCは次バージョンのJava 27リリースまでで、それ以降のアップデートでは有償のOTNに切り替わるので注意が必要です。
開発組織
関わった開発組織は、こんな感じになってます。日本企業ではNTT DataとFujitsuが貢献を続けています。今回NTT DataはGoogleよりも大きいですね。

JEP
大きめの変更はJEPでまとまっています。
https://openjdk.org/projects/jdk/26/
今回は10個のJEPが取り込まれました。正式採用は5個です。プレビューからの正式化もありません。プレビューは5個で、新たに入ったものはありません。
今回は次の4つのカテゴリにまとめてます(今回はAPIを先に言語を後にしています)
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)
API
APIの変更としてはセキュリティ系を除いて6つのJEPがあります。新しく入ったのは3つです。プレビューから正式化されたものはありません。またJEPになっていない変更もあまり多くないです。
500: Prepare to Make Final Mean Final
504: Remove the Applet API
517: HTTP/3 for the HTTP Client API
525: Structured Concurrency (Sixth Preview)
526: Lazy Constants (Second Preview)
529: Vector API (Eleventh Incubator)
小さいもの
JEPになっていないAPI変更で、動きがわかりやすいものを挙げます。
Comparatorのmin、maxメソッド
Comparatorにありそうでなかったmin、maxメソッドが入りました。
ProcessがAutoClosableに
ProcessがAutoClosableを実装して、try-with-resourceで使えるようになりました。
void main() {
try (Process p = new ProcessBuilder("cat").start();
var writer = p.outputWriter();
var reader = p.inputReader()) {
writer.write(haiku);
writer.close();
// Read all lines and print each
reader.readAllLines()
.forEach(IO::println);
var status = p.waitFor();
if (status != 0)
throw new RuntimeException("unexpected process status: " + status);
} catch (Exception e) {
System.out.println("Process failed: " + e);
}
}
static final String haiku = """
Oh, the sunrise glow;
Paddling with the river flow;
Chilling still, go slow.
""";
[JDK-8364361] [process] java.lang.Process should implement Closeable - Java Bug System
Robotのclickメソッド
LinuxではX11に代わるディスプレイサーバーとしてWaylandへの移行が進んでいます。JavaではProject Wakefieldとしてその対応が行われていますが、どうやらその対応でjava.awt.Robotに手が入っている模様。

このあたりだろうか。
[JDK-8280983] [XWayland] java.awt.Robot emulating keyboard/mouse events - Java Bug System
FoldCase対応のcompare、equals
こういうの
String a = "Fuß";
String b = "FUSS";
int cmpFoldCase = a.compareToFoldCase(b); // returns 0
int cmpIgnoreCase = a.compareToIgnoreCase(b); // returns > 0
InstanceのplusSaturationg
Instanceが扱えるMIN-MAXの範囲にクリップするplusメソッドです。
MIN、MAX自体も今回新たに追加されています。
[JDK-8366829] Add java.time.Duration constants MIN and MAX - Java Bug System
[JDK-8368856] Add a method that performs saturating addition of a Duration to an Instant - Java Bug System
500: Prepare to Make Final Mean Final
「FinalがFinalを意味するように準備」というJEPです。
Javaのfinalフィールドは、setAccessible(true)することによってリフレクションで書き換え可能なため、finalがFinalを意味していませんでした。
DIコンテナやモックツールなどがこの仕組みを使っていましたが、JVMはfinalフィールドが変更されることを前提に処理する必要があって、定数畳み込みなどの最適化が制限されていました。
つまり、安全性や性能が犠牲になっていたため、finalフィールドを変更できないようにしたいのだけど、まずは警告を出すところからというのがこのJEPです。
以前にも段階的に制約されています。
- Java 15 hidden classのfinalフィールドの変更不可
- Java 16 recordのfinalフィールドの変更不可
- Java 17 内部モジュールのfinalフィールドの変更不可
- Java 26 通常クラスのfinalフィールドの警告
- Java ?? 通常クラスのfinalフィールドの変更不可
試してみます。
// recordのfinalフィールドは変更できないので通常のクラス
class Person {
final String name;
final int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "name=%s age=%d".formatted(name, age);
}
}
void main() throws Exception{
var p = new Person("Alice", 30);
IO.println(p);
var name = Person.class.getDeclaredField("name");
name.setAccessible(true); // リフレクションでの変更を許可
try {
name.set(p, "Bob"); // リフレクションで値を変更
} catch(IllegalAccessException e){
IO.println("怒られた");
}
IO.println(p);
}
まず、Java 25で実行した場合、次のようにnameがBobに変わります。
% java test.java
name=Alice age=30
name=Bob age=30
Java 26の場合、警告がでます。
> java test.java
name=Alice age=30
WARNING: Final field name in class test$Person has been mutated reflectively by class test in unnamed module @17c1bced (file:/Users/naoki/test.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
name=Bob age=30
--enable-final-field-mutation=ALL-UNNAMEDをつけたら警告がなくなるよって書いてあるのでつけてみます。これはモジュールごとにfinalフィールドの変更を有効にするスイッチです。ここでは全ての無名モジュールについて有効にしています。
そうすると警告がなくなります。
> java --enable-final-field-mutation=ALL-UNNAMED test.java
name=Alice age=30
name=Bob age=30
--illegal-final-field-mutationをdenyにすると、setAccessibleではなくset時にエラーになってIllegalAccessExceptionが発生します。
> java --illegal-final-field-mutation=deny test.java
name=Alice age=30
怒られた
name=Alice age=30
--illegal-final-field-mutationをwarnにすると警告、allowにするとJava 25と同じようになります。
504: Remove the Applet API
Java 9で非推奨となったアプレットは、Java 11でappletviewerが削除されてJDK単体での実行手段がなくなり、Java 17で削除用に非推奨となり、Java 24でアプレットをサンドボックス実行するために重要であったセキュリティマネージャーが無効化され、このたび削除されることになりました。
517: HTTP/3 for the HTTP Client API
HTTP Client APIをHTTP/3に対応させます。
(URLクラスでのHTTPアクセスはHTTP/1.1のままです。)
デフォルトはHTTP/2のままで、次のようにしてHTTP/3を指定することができます。
var client = HttpClient
.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
リクエスト単位でも指定できます。
var request = HttpRequest
.newBuilder(URI.create("https://openjdk.org/"))
.version(HttpClient.Version.HTTP_3)
.GET().build();
実際に接続するコードは次のようになります。2回接続しています。
import module java.net.http;
void main(String[] args) throws Exception {
var url = args[0];
var client = HttpClient
.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
var req = HttpRequest
.newBuilder(URI.create(url))
.GET().build();
IO.println("connect to " + url);
for (int i = 0; i < 2; ++i) {
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
IO.println(res.version());
}
}
HTTP/3に対応してるサイトとしてGoogleにつないでみます。
>java http.java https://google.com/
connect to https://google.com/
HTTP_2
HTTP_3
一度目はHTTP/2で接続、2度目からHTTP/3になってますね。
HTTP/3に対応していないOpenJDKのサイトで試してみます。
java http.java https://openjdk.org/
connect to https://openjdk.org/
HTTP_2
HTTP_2
2回ともHTTP/2で接続しているようです。
Http3DiscoveryMode
HTTP/3はまだそんなに普及していないので、HTTP/3に対応していない場合にHTTP/2やHTTP/1.1で接続できる必要があります。
HTTP/3はUDP上のプロトコルで、HTTP/3に対応していないのであればそもそもUDPで待ち受けていないので、HTTP/2からHTTP/1.1へのように接続時にフォールバック情報を返すということができません。
Googleに接続した場合を見ると、最初はHTTP/2で接続して2回目からHTTP/3になっているようです。
>java http.java https://google.com/
connect to https://google.com/
HTTP_2
HTTP_3
パターンとして考えられるのは次のようなものがあります。
- HTTP/3(UDP)で接続してエラーが出たらHTTP/2(TCP)
- HTTP/3(UDP)とHTTP/2(TCP)に同時に接続して早い方
- HTTP/2(TCP)に接続してHTTP/3対応ヘッダーが入っていれば次回からHTTP/3(UDP)
- 常にHTTP/3(UDP)
HttpClientに標準として用意されているのは3.と4.です。このモードはH3_DISCOVERYをリクエストのオプションに指定すると変更できます。
次のようにHTTP_3_URI_ONLYを指定すると、HTTP/3で常に接続を行います。
var req = HttpRequest
.newBuilder(URI.create(url))
.setOption(HttpOption.H3_DISCOVERY,
HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY)
.GET().build();
>java http.java https://google.com/
connect to https://google.com/
HTTP_3
HTTP_3
HTTP/3に対応していないOpenJDKサイトでは、HTTP/3で接続できないのでSSLHandshakeExceptionが発生します。
>java http.java https://openjdk.org/
connect to https://openjdk.org/
Exception in thread "main" javax.net.ssl.SSLHandshakeException: QUIC connection establishment failed
メッセージがQUIC connection establishment failedとなってますが、HTTP/3がQUICというプロトコル上に実装されていて、その層で失敗していることを表します。
また、SSLHandshakeExceptionが発生するのは、QUICがTLSを統合したプロトコルで、最初にTLSのハンドシェイクを行うためです。例外名はTLSが当初SSLと呼ばれていたことによります。
デフォルトの、最初はHTTP/2で接続して次からHTTP/3というのはALT_SVCになります。また、デフォルトでは実装にまかせるANYになっており、OpenJDKではALT_SVCになるようです。
| Http3DiscoveryMode | 接続方法 |
|---|---|
| ANY | 実装固有の接続方法。現状ではALT_SVC |
| ALT_SVC | 最初にHTTP/2で接続して、HTTP/3が使えるというヘッダーが返ってきたらそのHTTPClientでの次回リクエストからHTTP/3 |
| HTTP_3_URI_ONLY | HTTP/3のみを使用 |
詳しくはHttp3DiscoveryModeのJavadocを。
https://docs.oracle.com/en/java/javase/26/docs/api/java.net.http/java/net/http/HttpOption.Http3DiscoveryMode.html
実装に関してはこちらのチケットを
https://bugs.openjdk.org/browse/JDK-8350588
525: Structured Concurrency (Sixth Preview)
Java 19でIncubatorとして含まれていましたが、Java 25で5th Previewになり、Java 26では細かい修正が行われつつ6th Previewになりました。
並列処理では、複数の処理を実行するときに、両方が終われば正常終了とか、どちらか片方が終われば終了だとか、どちらか一方でも例外が発生したら終了だとか、同時に行う処理で連動することがあります。
しかし、これを既存のjoinやwaitなどで制御しようとすると、実際にはjoinからwaitへのGo Toを書くようなコードになって、処理が追えなくなります。
そこで導入されるのが構造化並列性といいます。
こんな感じ。詳しくはあとで書きます!(Java 19のときから言ってる・・・)
Response handle() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
Subtask<String> user = scope.fork(() -> findUser());
Subtask<Integer> order = scope.fork(() -> fetchOrder());
scope.join() // Join both forks, propagatiton exceptions
// Both subtasks have succeeded, so compose their results
return new Response(user.get(), order.get());
}
}
SubtaskはSupplierを継承しているので、Supplierとして扱うほうがいいかもしれません。
526: Lazy Constants (Second Preview)
Java 25でStable Valueだった機能がメソッドが整理されLazy Constantsとして2nd Previewになりました。
クラス名もStableValueからLazyConstantになっています。
Javaでは、不変な値を保持する仕組みとしてfinalがあります。
final Logger logger = Logger.create();
しかし、クラスやインスタンスの初期化時にすべての不変値を初期化してしまうと、起動が遅くなったりネットやファイルへのアクセスが集中することにもなります。
そこで、値が必要になったときに初期化したいということが多くあります。
そのような場合、アクセッサを介して値を得るようにすることで、遅延初期化を実現します。
private Logger logger;
Logger getLogger() {
if (logger == null) {
Logger.create();
}
return logger;
}
けれども、このようにするとフィールドからfinalが外れることでJVMでの最適化が効きにくくなります。また、マルチスレッドを考慮すると少し複雑なコードが必要になります。もしくは、マルチスレッドで初期化が多重に行われても問題ないかどうか判断して、実質的な問題がなければそのままスレッドセーフではないコードを使うことになります。
そういった場合に使えるのが今回導入されるLazyConstantです。
final LazyConstant<Logger> logger = LazyConstant.of(() -> Logger.create());
logger.get().info("started");
...
logger.get().info("finish");
このときofメソッドで指定した初期化コードは1度だけしか呼び出されないことがLazyConstantによって保証され、スレッドセーフになります。また、JVMでの最適化も行われるようになります。
不変値のコレクションを扱いたいときはofLazyが使えます。
final List<Connection> pool = List.ofLazy(10, idx -> new Connection(idx + "th connection");
こうするとpool.get(3)などとしてリストにアクセスすると、初回にそのインデックスに対応する値が初期化されるようになります。
LazyConstantはプレビューなので、関連機能を使うときには--enable-previewが必要です。
529: Vector API (Eleventh Incubator)
AVX命令のような、複数のデータに対する計算を同時に行う命令をJavaから利用できるようになります。
使うためには実行時やコンパイル時に--add-modules jdk.incubator.vectorをつける必要があります。
Java 16でインキュベータとして導入されたPanamaプロジェクトの残る片割れですが、11th Incubatorになりました。Project Valhallaのvalue classを使いたいようで、関連JEPがpreviewになるまではIncubatorのままということです。
基本的な使い方は次のようになります。
import jdk.incubator.vector.*;
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
void vectorComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i += SPECIES.length()) { // SPECIES.length() = 256bit / 32bit -> 8
VectorMask<Float> m = SPECIES.indexInRange(i, a.length); // 端数がマスクされる
// a.lengthが11でiが8のとき最初の3つしか要素がないので [TTT.....]
// FloatVector va, vb, vc;
FloatVector va = FloatVector.fromArray(SPECIES, a, i, m);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i, m);
FloatVector vc = va.mul(va).
add(vb.mul(vb)).
neg();
vc.intoArray(c, i, m);
}
}
言語機能
言語機能の変更としては新しいものはなく正式化されたものもありません。
530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)
530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)
Java 23でプレビューとして入りましたが、機能的な変更なく4thプレビューになりました。仕様としてはあいまいだった部分が明確化されました。
instanceof
instanceofでプリミティブを使うとき、値が欠落しないかどうかが判定基準になります。
jshell> int i = 128
i ==> 128
jshell> i instanceof byte
$2 ==> false
jshell> i = 127
i ==> 127
jshell> i instanceof byte
$4 ==> true
実数型を整数型で判定するときは、精度落ちするかどうかが判定されます。
jshell> double d = 1
d ==> 1.0
jshell> d instanceof int
$2 ==> true
jshell> d = 1.2
d ==> 1.2
jshell> d instanceof int
$4 ==> false
ただし、ラッパークラスのオブジェクトの場合はその基本型のときだけtrueになります。
jshell> Long num = 123L
num ==> 123
jshell> num instanceof int
| エラー:
| 不適合な型: java.lang.Longをintに変換できません:
| num instanceof int
| ^-^
jshell> num instanceof long
$7 ==> true
そしてパターンマッチとして使えるわけですね。
jshell> long num = 123
num ==> 123
jshell> var out = new ByteArrayOutputStream()
out ==>
jshell> if (num instanceof byte b) out.write(b)
jshell> out.toByteArray()
$21 ==> byte[1] { 123 }
jshell> num = 1234
num ==> 1234
jshell> if (num instanceof byte b) out.write(b)
jshell> out.toByteArray()
$24 ==> byte[1] { 123 }
switch
このパターンマッチがswitchでも使えるということなんですけど、定数パターンもあるので、switchですべての型が使えるようになったということになります。
jshell> long num = 1234
num ==> 1234
jshell> switch (num) { case int n -> "いんと"; default -> "それ以外";}
$25 ==> "いんと"
jshell> num = 30_0000_0000L
num ==> 3000000000
jshell> switch (num) { case int n -> "いんと"; default -> "それ以外";}
$27 ==> "それ以外"
if式のようなこともできますね。
boolean flag = isFoo();
var s = switch (flag) {
case true -> "たった";
case false -> "おりた";
}
仕様の明確化
例えば次のようなコードは、最初のcase int iがすべての場合に処理をしてしまうのでcase byte bに辿り着きません。
int a = 30;
IO.println(switch(a) {
case int i -> "32bit";
case byte b -> "8bit";
}
なので、コンパイル時エラーになるのですが、Java 25まではそのことが仕様に明示されていませんでした。Java 26では明示されました。
JVM
JVMの変更として、2つJEPが導入されています。
516: Ahead-of-Time Object Caching with Any GC
522: G1 GC: Improve Throughput by Reducing Synchronization
516: Ahead-of-Time Object Caching with Any GC
JEP-483で導入されたAOTキャッシュは、ファイルをメモリのように扱うmmap利用してメモリ上のオブジェクトデータをそのまま読み書きしていました。
しかしZGCはmmapと相性がわるく、そのためAOTキャッシュも使えませんでした。
また、GCのオブジェクト格納方法に依存するため、キャッシュは作成時と同じGCでしか使えませんでした。
今回、GCに依存しないキャッシュ保存方式を導入して、ZGCや将来導入されるかもしれない新しいGCにも対応できるようにしました。
オブジェクトデータをメモリ領域ごと読み込むのではなく、オブジェクトごとにストリーム形式で読み込むことでどんなGCにも対応できます。
G1GCなどではデフォルトでmmap形式のキャッシュを作成しますが、-XX:+AOTStreamableObjectsをつけてキャッシュを作成すると、G1GCなどを利用しているときにもストリーム形式のキャッシュファイルを作成します。
522: G1 GC: Improve Throughput by Reducing Synchronization
G1GCでアプリケーションスレッドとGCスレッドの同期処理を減らすことで、G1 GCのスループットを向上させます。
G1 GCではフィールドが持つオブジェクト参照が更新されたことをカードテーブルというデータ構造で管理しています。カードテーブルを見れば更新されたオブジェクト参照がわかるため効率的にGC処理ができます。
けれども、更新が多いアプリケーションの場合は、アプリケーションの処理とGCの処理でカードテーブルの利用が衝突して同期が必要になり、スループットが悪くなります。
カードテーブルを2つ持つことで、同期を減らします。
アプリケーションはフィールドのオブジェクト参照が変更になった場合、片方のカードテーブルに情報を書き込みます。
GCの処理時には、もう片方のカードテーブルを読み込むようにします。そうすることでカードテーブルアクセスの衝突を減らします。
そのカードテーブルの全ての処理が終わってもGC処理に余裕がある場合には、カードテーブルを交換して残りのカードテーブルの処理を進めます。
Security
セキュリティ、暗号関係では次のJEPが導入されています。
524: PEM Encodings of Cryptographic Objects (Second Preview)
524: PEM Encodings of Cryptographic Objects (Second Preview)
暗号鍵や証明書などの暗号オブジェクトを送受信するための表現形式として、Privacy-Enhanced Mail(PEM)形式がよく使われています。PEM形式は名前のとおり、メール用に設計されましたが、他の用途にも使われるようになっています。
このJEPで、PEM形式へのエンコードやデコードを行うAPIを定義します。



