毎度おなじみ半年ぶりのJavaのアップデートです。
OpenJDK的にはLTSかどうかは関係なく、LTSかどうかはOracleなどのJDKディストリビューターによります。OracleがJava 25をLTSとするので、右にならえで他のディストリビューターもLTSにしてますね。
OpenJDKはLTSとは関係ないとはいうものの、実際にはLTSに間に合わせて機能を入れ込むということはありそうです。実際、Java 25のStandard JEPは12と結構な数になっています。
Java 25のJEPの一覧はこちら。
- 470: PEM Encodings of Cryptographic Objects (Preview)
- 502: Stable Values (Preview)
- 503: Remove the 32-bit x86 Port
- 505: Structured Concurrency (Fifth Preview)
- 506: Scoped Values
- 507: Primitive Types in Patterns, instanceof, and switch (Third Preview)
- 508: Vector API (Tenth Incubator)
- 509: JFR CPU-Time Profiling (Experimental)
- 510: Key Derivation Function API
- 511: Module Import Declarations
- 512: Compact Source Files and Instance Main Methods
- 513: Flexible Constructor Bodies
- 514: Ahead-of-Time Command-Line Ergonomics
- 515: Ahead-of-Time Method Profiling
- 518: JFR Cooperative Sampling
- 519: Compact Object Headers
- 520: JFR Method Timing & Tracing
- 521: Generational Shenandoah
目だったところでいうと、Scoped Valueがやっと正式になりましたね。でも、もう1つのStructured ConcurrencyがまだPreview...
ニーズ的にはThreadLocalを置き換えるScoped Valueの方が高いから、こちらが優先されたのかもしれません。
初学者向け機能であるJEP 511とJEP 512が正式導入されたのもうれしいところです。
Project LeydenのJEP 514とJEP 515も正式になって、GraalVMのネイティブイメージではない選択肢が増えてきたのはよいことですね。
JEPについての詳しい解説は「JEPで語る」に書く予定です。
さて、JEPで語れない方です。Java 25はそこそこ多いです。
Java 25はjava.baseモジュール以外にも変更があるので、そちらも紹介します。しかし、セキュリティ関連はいつものごとく省略させてください。
廃止になったAPI
Java 24で削除されたAPIはメソッドとコンストラクタが1つずつ。いずれもSwing関連のAPIです。
メソッド
SwingのLoo&FeelのSynthに関するメソッドが削除されました。
- javax.swing.plaf.synth.SynthLookAndFeel.load(URL url)
このメソッドはLook&Feelの設定を書いたファイルを読み込むためのものなのですが、安全性に問題があるということでJava 21でforRemoval=trueになっていました。
まぁ、Look&FeelのSynthを使ったアプリケーションはほぼないと思いますし、他の手段もあるので、困る人も少ないでしょう。
コンストラクタ
SwingのスライダーのUIクラスのコンストラクターが削除されました。
- javax.swing.plaf.basic.BasicSliderUI.BasicSliderUI()
このデフォルトコンストラクターは間違って公開されてしまったらしいですよw
本来は引数にJSliderクラスをとるコンストラクターを使用するので、まぁ使っている人はいないとは思いますが、使っていた場合は引数をとる方のコンストラクターに書き換えてください。
廃止予定に追加されたAPI
Java 25では、Java 24でのセキュリティマネージャーの削除に伴って、パーミッション系のクラスがforRemoval=trueになっています。
クラス
- java.io.FilePermission
- java.io.SerializablePermission
- java.lang.RuntimePermission
- java.lang.management.ManagementPermission
- java.lang.reflect.ReflectPermission
- java.net.NetPermission
- java.net.URLPermission
- java.nio.file.LinkPermission
- java.security.SecurityPermission
- java.security.UnresolvedPermission
- java.util.PropertyPermission
- java.util.logging.LoggingPermission
- javax.management.MBeanPermission
- javax.management.MBeanServerPermission
- javax.management.MBeanTrustPermission
- javax.management.remote.SubjectDelegationPermission
- javax.net.ssl.SSLPermission
- javax.security.auth.AuthPermission
- javax.security.auth.PrivateCredentialPermission
- javax.security.auth.kerberos.DelegationPermission
- javax.security.auth.kerberos.ServicePermission
例外
- javax.management.modelmbean.XMLParseException
メソッド
- java.net.HttpURLConnection.getPermission
- java.net.URLConnection.getPermission
- javax.management.modelmbean.DescriptorSupport.toXMLString
もし、パーミッション系のAPIを使っているのであれば、早めに他の手段に置き換えた方がいいですね。
追加/変更されたAPI
今回のJava 25は、Java 24とは異なりいろいろ追加されています。しかも、JEPに関連のないAPIの変更がいろいろあります。
とはいうものの、使うかと言われたら微妙なものが多いですね。
今回もjava.baseモジュール以外の変更は少ないので、java.baseモジュールだけです。また、セキュリティ系のAPIは省略させてください。
java.base/java.ioパッケージ
JEP 512で正式に導入されたIOクラスは、Java 24まではjava.ioパッケージだったのですが、Java 25からjava.langパッケージに変更されました。また、同様にConsoleクラスにPreviewでprint(Object obj)メソッドなどが追加されていましたが、結局キャンセルになったようです。
Readerクラス
なぜかJava 25にもなって、Readerクラスに一括読み込みのメソッドが追加されました。
- String readAllAsString()
- Line<String> readAllLines()
今までReader系のクラスでは逐次読み込みばかりだったので、便利なのは確かなのですが、なぜ今??
やっぱり、Files.readAllLinesメソッドを使うようになってから、逐次読み込みなんか書いてられないということなのでしょうか?
おもしろいのは、JavadocのThrowsにOutOfMemoryError例外が記載されていること。Stringクラスがあふれちゃうことがあるかららしいです。
java.base/java.langパッケージ
Java 25では、Unicodeのバージョンアップはないので、Unicodeに関するクラスや列挙型には変更ありません。
その代わりといってはなんですが、Math/StrictMathクラスにメソッドがいろいろ追加されています。
また、java.ioパッケージのところでも言及しましたが、IOクラスがjava.langパッケージに変更されてきています。
CharSequenceインタフェース
文字列の部分配列を取得するためのメソッドが追加されました。インタフェースなので、もちろんdefaultメソッドです。
- default void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
getCharsメソッドはStringクラスにはあったので、それをCharSequenceでも使えるようにした感じですね。
引数のdstに結果が返るというのは今時ではないですけど、StringクラスのgetCharsメソッドははじめからあるメソッドなのでしかたないです。
IOクラス
JEP 512で、標準入出力からの入出力を行うために導入されたのがIOクラスです。Java 24の時とパッケージが変わっただけで、メソッドは変更なしです。
- static void print(Object obj)
- static void println()
- static void println(Object obj)
- static String readln()
- static String readln(String prompt)
今までSystem.out.println("Hello, World");と書いていたのが、IO.println("Hello, World!");になります。
初心者にとって学ぶことが少しでも減れば、学ぶときのハードルが下がりますね。
Mathクラス/StrictMathクラス
Math/StrictMathクラスにメソッドが7つ追加されました。
歴史的な経緯でMathクラスとStrictMathクラスは別々のクラスになっていますが、実際にはStrictMathクラスは内部でMathクラスを呼び出しているだけなので、今となっては同じクラスとして扱っても構わないです。
- static int powExact(int x, int n)
- static long powExact(long x, int n)
- static int unsignedMultiplyExact(int x, int y)
- static long unsignedMultiplyExact(long x, int y)
- static long unsignedMultiplyExact(long x, long y)
- static int unsignedPowExact(int x, int n)
- static long unsignedPowExact(long x, int n)
今まで、累乗演算は引数がdoubleのものだけでしたが、整数型が加わりました。また、累乗計算も含めて、符号なし計算も追加されています。
これまで、Mathクラスでは符号なし計算はunsignedMultiplyHighメソッドだけだったので、今後も増えるのかもしれません。
ScopedValueクラス
JEP 506でPreviewがはずれて、正式に導入されたScopedValueクラスです。
Java 24との違いはorElseメソッドの引数にnullが許されなくなったことぐらいですね。
ScopedValueクラスについてはここではなく、JEPで語る方で紹介します。
StableValueインタフェース
JEP 502でPreviewとして追加されたのが、StableValueインタフェースです。
同じパッケージにScopedValueとStableValueという同じような名前のクラス/インタフェースができたので、ごっちゃになりやすくて困ってますw
StableValueインタフェースもJEPで語る方で紹介します。
java.base/java.lang.classfileパッケージ
ClassFileクラスにバージョンを表す定数が追加されています。
ClassFileクラス
ClassFileクラスでは、クラスファイルのバージョンを表す定数が定義されていますが、それが追加されました。
- static final int JAVA_25_VERSION
この値はJava 25だから25というわけではなく、クラスファイルのバージョンの69になっています。
java.base/java.lang.classfile.constantpoolパッケージ
java.lang.classfile.constantpoolパッケージではクラスファイルのコンスタントプールに関する構造体を表すインタフェースが定義されています。
このコンスタントプールの構造体を表すインタフェースは、java.lang.constantパッケージで定義されているインタフェースをモデル化したものです。そこで、この対応関係を調べるためのメソッドが追加されました。
Class-File APIは新しいAPIですが、新しいからこそ、まだいろいろ追加されるのかもしれません。
ClassEntryインタフェース
コンスタントプールで参照型を表すCONSTANT_Class_info構造体をモデル化したのがClassEntryインタフェースです。ClassEntryインタフェースはjava.lang.constant.ClassDescインタフェースによってモデル化されたインタフェースになります。
このClassDescインタフェースとマッチするかどうかを調べるためのメソッドが追加されています。
- boolean matches(ClassDesc desc)
ClassEntryオブジェクトとClassDescオブジェクトが表している参照型クラスが同じであればtrueが返ります。また、引数のdescがプリミティブ型の場合はfalseになります。
MethodTypeEntryインタフェース
コンスタントプールでCONSTANT_MethodType_info構造体をモデル化したのがMethodTypeEntryインタフェースです。MethodTypeEntryインタフェースはjava.lang.constant.MethodTypeDescインタフェースによってモデル化されたインタフェースです。
- boolean matches(MethodTypeDesc desc)
同じメソッドを表している場合、trueが返ります。
ModuleEntryインタフェース
コンスタントプールでCONSTANT_Module_info構造体をモデル化したのがModuleEntryインタフェースです。対応するのが、java.lang.constant.ModuleDescインタフェースです。
- boolean matches(ModuleDesc desc)
同じモジュールを表している場合、trueが返ります。
PackageEntryインタフェース
コンスタントプールでCONSTANT_Package_info構造体をモデル化したのがPackageEntryインタフェースです。対応するのが、java.lang.constant.PackageDescインタフェースです。
- boolean matches(PackageDesc desc)
同じパッケージを表している場合、trueが返ります。
StringEntryインタフェース
コンスタントプールでCONSTANT_String_info構造体をモデル化したのがStringEntryインタフェースです。Stringクラスは参照型なので、本来であれば対応するのはClassDescインタフェースですが、文字列リテラルは特別扱いです。
- boolean equalsString(String value)
StringEntryインタフェースはmatchesメソッドではなく、文字列と直接比較するequalsStringメソッドが追加されています。
Utf8Entryインタフェース
コンスタントプールでは、文字列リテラルだけでなくクラス名やメソッド名はUTF-8で保持されています。それを表すCONSTANT_UTF8_info構造体をモデル化したのがUtf8Entryインタフェースです。
Utf8EntryインタフェースはすでにequalsStringメソッドは定義してあるのですが、クラス名やメソッド名とマッチさせるメソッドが追加されました。
- boolean isFieldType(ClassDesc desc)
- boolean isMethodType(MethodTypeDesc desc)
java.base/java.lang.reflectパッケージ
java.lang.reflectパッケージはいつも通り新しいバージョンに合わせた定数が追加されているのですが、Java 25ではアクセスフラグにも追加があります。
AccessFlag列挙型
アクセスフラグのセットを作成するmaskToAccessFlagsメソッドに、クラスファイルのバージョンを指定できるオーバーロードが追加されました。
- static Set<AccessFlag> maskToAccessFlags(int mask, AccessFlag.Location location, ClassFileFormatVersion cffv)
AccessFlag.Location列挙型
アクセスフラグを適用できる場所を示しているのがAccessFlag.Location列挙型です。
列挙型ですが、メソッドが追加されました。
- int flagsMask()
- int flagsMask(ClassFileFormatVersion cffv)
- Set<AccessFlag> flags()
- Set<AccessFlag> flags(ClassFileFormatVersion cffv)
たとえば、MODULEであればフラグマスクはACC_OPEN | ACC_SYNTHETIC | ACC_MANDATEDになります。
クラスファイルのバージョンによって使用できるフラグマスクが異なるので、バージョンを指定するオーバーロードがあるわけです。
ClassFileFormatVersion列挙型
いつものように、Java 25に対応する定数が追加されました。
java.base/java.nioパッケージ
なぜかCharBufferクラスにメソッドが追加されました。
CharBufferクラス
一括取得のメソッドが追加されたのですが、今まで使用していたgetメソッドと何が違うの?という感じです。
- public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
Javadocにも書かれているのですが、このメソッドはgetメソッドを使った次の式と同じ動作になります。
buffer.get(position() + srcBegin, dst, dstBegin, srcEnd - srcBegin);
記述が簡単になったというわけでもないですし、何のために追加されたんでしょうね。
java.base/java.utilパッケージ
CurrencyクラスとTimeZoneクラスにStreamインタフェースを返すユーティリティメソッドが追加されました。
Currencyクラス
使用可能な通貨を返すメソッドが追加されています。
- static Stream<Currency> availableCurrencies()
これまではgetAvailableCurrenciesメソッドが使えましたが、このメソッドは返り値がSetインタフェースでした。availableCurrenciesメソッドは返り値の型がStreamインタフェースなので、Stream APIで扱いやすくなっています。
TimeZoneクラス
TimeZoneクラスでも使用可能なIDを返すメソッドが追加されています。
- static Stream<String> availableIDs()
- static Stream<String> availableIDs(int rawOffset)
TimeZoneもgetAvailableIDsメソッドがありましたが、戻り値の型がStringクラスの配列でした。追加された2メソッドはStreamインタフェースになっています。
java.base/java.util.concurrentパッケージ
ForkJoinPoolクラスに大きな変更がありました。
ForkJoinPoolクラス
ForkJoinPoolクラスはこれまでExecutorインタフェース、ExecutorServiceインタフェース、AutoClosableインタフェースを実装していましたが、新たにScheduledExecutorServiceインタフェースを実装するようになりました。
このため、ScheduledExecutorServiceインタフェースで定義されているメソッドが追加されています。
- void cancelDelayedTasksOnShutdown()
- long getDelayedTaskCount()
- ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
- <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
- ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
- ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
- <V> ForkJoinTask<V> submitWithTimeout(Callable<V> callable, long timeout, TimeUnit unit, Consumer<? super ForkJoinTask<V>> timeoutAction)
ScheduledExecutorServiceインタフェース由来のメソッドはscheduleで始まる4つのメソッドです。
scheduleメソッドはタスク処理の開始を指定された時間だけ遅延させるメソッドです。オーバーロードは引数がRunnableインタフェースか、Callableインタフェースの違いです。
たとえば、10秒後に現在時刻を出力するのであれば、次のようになります。
jshell> var pool = new ForkJoinPool(2)
jshell> pool.schedule(() -> IO.println(LocalDateTime.now()), 10, TimeUnit.SECONDS)
$2 ==> java.util.concurrent.DelayScheduler$ScheduledForkJoinTask@7a07c5b4[Wrapped task = $Lambda/0x0000000088048858@26a1ab54]
jshell> IO.println(LocalDateTime.now())
2025-09-12T18:42:45.527033900
jshell> 2025-09-12T18:42:53.898474800
比較のために、IO.printlnをもう1度実行していますが、手で打ち込んでいるので遅いですねw
scheduleAtFixedRateメソッドとscheudleWithFixedDelayの違いは、前者が同じ周期でタスクを実行するのに対し、後者はタスクが終わってから次のタスクまでの遅延が常に同じということです。
たとえば10秒ごとで処理に1秒かかる場合、scheduleAtFixedRateメソッドであれば10秒ごとにタスクが処理されるのに対し、scheduleWithFixedDelayメソッドだと11秒ごとに処理が行われます。
jshell> var pool = new ForkJoinPool(2)
jshell> pool.scheduleAtFixedRate(() -> IO.println(LocalDateTime.now()), 0, 10, TimeUnit.SECONDS)
$4 ==> java.util.concurrent.DelayScheduler$ScheduledForkJoinTask@6433a2[Wrapped task = $Lambda/0x0000000082048210@5910e440]
jshell> 2025-09-12T19:32:30.296824400
2025-09-12T19:32:40.258892800
2025-09-12T19:32:50.264887400
2025-09-12T19:33:00.258888800
2025-09-12T19:33:10.257071800
jshell> var pool = new ForkJoinPool(2)
jshell> pool.scheduleWithFixedDelay(() -> { IO.println(LocalDateTime.now()); try { Thread.sleep(1_000); } catch (Exception e){}}, 0, 10, TimeUnit.SECONDS)
$4 ==> java.util.concurrent.DelayScheduler$ScheduledForkJoinTask@7a07c5b4[Wrapped task = $Lambda/0x0000000033048858@26a1ab54]
jshell> 2025-09-12T19:48:54.864989800
2025-09-12T19:49:05.866353500
2025-09-12T19:49:16.869035100
2025-09-12T19:49:37.873515600
ちょっと分かりにくいかもしれないですけど、後者は11秒周期になっています。
周期的なタスクを中止するには、返り値のScheduledFutureオブジェクトに対し、cancelメソッドをコールします。
cancelDelayedTasksOnShutdownメソッドは、ForkJoinPoolオブジェクトをシャットダウンする時に遅延させた未実行のタスクをキャンセルする場合に使用します。
getDelayedTaskCountメソッドは、遅延させた未実行のタスクの数を返すメソッドです。
そして、最後のsubmitWithTimeoutメソッドだけがScheduledExecutorServiceインタフェースに関連のないメソッドで、タスク処理にタイムアウトを設定できるメソッドです。
ForkJoinPoolは、ForkJoinTaskと合わせることで分割統治とWork-Stealingでタスクを並行処理するために導入されたのですが、Work-Stealingだけ使いたいということが増えてきているんでしょうね。
java.base/java.util.zipパッケージ
DeflaterクラスとInflaterクラスがAutoClosableインタフェースを実装するようになりました。これによりcloseメソッドが追加されています。
Deflaterクラス/Inflaterクラス
AutoClosableインタフェースの実装に伴い、closeメソッドが追加されました。
これまでは、処理の終了時にはendメソッドを使用していましたが、AutoClosableインタフェースを実装したことによりtry-with-resources構文を使用できるようになりました。
その他
Javadoc
Javadocの検索候補の表示が変更されています。
たとえば、検索窓にStrまで入力した場合、Java 24だと以下のようになります。
これに対して、Java 25だと次のようになります。
Java 24では検索候補のパッケージやクラスの前にモジュールやパッケージが表示されていますが、Java 25では検索候補がまず表示されます。そのモジュールやパッケージはウィンドウの幅が十分にある場合は右側、なければ検索候補の下に表示されるようになりました。
ちなみに、Java 26でもJavadocが変更されていて、検索候補を右クリックで別タブでの表示などを選択できるメニューが表示されます。他にも、細かな変更がされています。
すでに、実装されているので、Java 26のアーリーアクセスビルドで試すことができます。
まとめ
APIの変更は多いものの、普通のアプリケーションでは使わなそうなものが多い感じです。
IOクラスが一番使うかもしれませんが、ほとんどの人はSystem.outを使い続けるような気が...
並行処理を書くことがあるのであれば、ScopedValueクラスは使うかもしれません。しかし、そもそもThreadLocalクラスでさえ通常は使わないので、ScopedValueクラスを使うとしても限定的でしょうね。
というか、こういう並行処理でこういう複数のオブジェクトで状態を共有というのは、スケールしなくなるし、いいことはないです。ThreadLocalクラスが使われているところをScopedValueクラスに置き換えるということはあるかもしれませんが、まずはその設計を見直した方がいいような気が...
さて、次のエントリーではJEPに関して簡単な説明を加えていく予定です。