長く使ってきた new Socket(address, port, false) の形が、JDK 25 に上げると実行時に次の例外で止まります。
Exception in thread "main" java.lang.IllegalArgumentException: Socket constructor does not support creation of datagram sockets
at java.base/java.net.Socket.<init>(Socket.java:448)
at java.base/java.net.Socket.<init>(Socket.java:431)
at App.main(App.java:6)
コンパイルは通ります(javac は [removal] Socket(InetAddress,int,boolean) in Socket has been deprecated and marked for removal という警告を出すだけです)。落ちるのは実行時で、java の起動自体は成功しています。原因は、Socket(InetAddress, int, boolean)(および Socket(String, int, boolean))の第3引数 stream に false を渡す使い方が、JDK 25 で拒否されるようになったことです。この形は UDP(データグラム)ソケットを作るための古い書き方で、直し方は用途に合った DatagramSocket へ置き換えることです。
// 旧:Socket の第3引数 false で、指定 address:port に「接続した」データグラムソケットを作っていた
// Socket socket = new Socket(address, port, false);
// 新:DatagramSocket を作り、旧コンストラクタが接続していたリモート endpoint へ connect する
DatagramSocket socket = new DatagramSocket();
socket.connect(address, port);
new DatagramSocket(port, address) のように引数を入れ替えるだけの置換は誤りです。それはローカルの port・address に束縛するだけで、旧コンストラクタが行っていたリモートへの接続を再現しません(後述)。
なぜ起きるのか:stream=false は「データグラムソケットを作る」指定だった
Socket(InetAddress, int, boolean) の第3引数 stream は、true なら TCP(ストリーム)ソケット、false なら UDP(データグラム)ソケットを作る、という意味でした。JDK 1.1 以来の API で、false を渡すと、指定した address:port に接続したデータグラムソケットが Socket 型で返ります(isConnected() が true、getPort() がその接続先ポートを返します。実測で確認)。ローカルのポートは OS が割り当てます。
この stream=false の使い方は早くから非推奨(削除予定)で、java.net.Socket のドキュメントは「データグラムには DatagramSocket を使うこと」を案内していました。JDK 25 は、この非推奨だった経路を実行時に IllegalArgumentException で拒否するようにしました。Socket constructor does not support creation of datagram sockets(Socket のコンストラクタはデータグラムソケットの生成をサポートしない)というメッセージは、まさに「stream=false でデータグラムを作る道はもう無い」という意味です。
stream=true を渡す(あるいは boolean を取らない new Socket(address, port) を使う)TCP の用途は影響を受けません。落ちるのは false を渡していた場合だけです。
なぜ JDK 25 で出るのか
境界は JDK の版で、コードでもネットワークの状態でもありません。同じ new Socket(addr, port, false) を、JDK 24 までは(非推奨警告つきで)動かし、JDK 25 で例外にします。
| JDK | new Socket(addr, port, false) の挙動 |
|---|---|
| 17 / 21 / 24 | 非推奨警告つきでデータグラムソケットを作る(実行は成功) |
| 25 | IllegalArgumentException: Socket constructor does not support creation of datagram sockets(実行時に停止) |
そのため、次のような食い違いで表面化します。
- ローカルやビルドは古い JDK、実行環境が JDK 25:コンパイルは JDK 25 でも通る(警告だけ)ので、ビルドは成功します。JDK 25 で実際にそのコード経路を通ったときにはじめて例外になります。テストがその経路を踏んでいないと、本番で初めて落ちることもあります。
- ベースイメージや CI のランタイムを JDK 25 に上げた:コード側は何も変えていないのに、同じ経路が例外を投げるようになります。
まず java -version で実行している JDK を確認します。25 以降で、コードに new Socket(..., false) があれば、この記事の変更に当たっています。コンパイル時の [removal] 警告も、同じ箇所を指しています。
直し方:DatagramSocket に置き換える
データグラム(UDP)を使いたいのなら、その用途の型である DatagramSocket を使います。旧コンストラクタは指定 address:port に接続したソケットを返していたので、同じ意味にするには DatagramSocket を作ってから connect します。
import java.net.DatagramSocket;
import java.net.InetAddress;
DatagramSocket socket = new DatagramSocket(); // ローカルは OS 任せ(旧コンストラクタと同じ)
socket.connect(address, port); // 旧 Socket が接続していたリモート endpoint
引数を入れ替えた new DatagramSocket(port, address) は等価ではありません。 DatagramSocket(int, InetAddress) の2引数はローカルの bind ポートと bind アドレスで、生成されるソケットは未接続(isConnected() が false)です。ここに旧コードのリモート port・address を渡すと、リモートに接続する代わりにそのポートをローカルで bind しようとするため、意味が変わり、ポート競合や送信先未設定になります。リモートへの接続を保つなら、上のように connect(address, port) を使います(DatagramSocket()+connect が旧 Socket(address, port, false) と同じ「リモート接続済み・ローカルは OS 割り当て」になることは実測で確認しました)。ローカルの特定ポートに bind したい事情があるときだけ、new DatagramSocket(localPort, localAddress) を使い、その後に connect(remoteAddress, remotePort) します。
置き換えたあとは、変数の型が Socket から DatagramSocket に変わるので、その先で getInputStream() / getOutputStream()(Socket のストリーム API)を呼んでいた箇所は DatagramSocket の send(DatagramPacket) / receive(DatagramPacket) に直します。stream=false で作っていたソケットは実体がデータグラムで、データグラムソケットはストリーム API を持たないため、その経路は以前の版でも実行時に失敗していたはずです。
切り分け(うまくいかないとき)
- エラーが
Unrecognized VM optionやUnrecognized option:で、main()の前に止まる:それは JVM の起動フラグの問題で、この記事のライブラリ API の例外とは別です。JVM の起動時に-XX:フラグで止まるなら Unrecognized VM option ‘UseBiasedLocking’(JDK 17 → 21)、-Xの launcher オプションで止まるなら Unrecognized option: -Xnoagent(JDK 23 以降) を参照してください。本記事のエラーは JVM が起動してから、そのコード行を実行した時点で投げられます。 - 本当に TCP ソケットが欲しかった:第3引数に
trueを渡していた、あるいは渡すつもりだったのなら、new Socket(address, port)(booleanを取らないコンストラクタ)を使います。TCP の用途は JDK 25 でも影響を受けません。落ちるのはfalseを渡していた場合だけです。 - コンパイル時に
[removal]警告は出るが例外にはならない:まだ JDK 24 以前で実行しています。警告は「この使い方は削除に向かっている」という予告で、JDK 25 で実際に例外になります。警告のうちにDatagramSocketへ移しておくと、25 に上げたときに落ちません。 Socketを直接は書いていないのにこの例外が出る:使っているライブラリの内部が古いSocket(..., false)を呼んでいる可能性があります。スタックトレースのjava.net.Socket.<init>の下(呼び出し元)を見て、どのライブラリの何行目から来ているかを確認します。自分のコードでないなら、そのライブラリの JDK 25 対応版がないかを見ます。
検証環境
eclipse-temurinの JDK 25.0.3(digest 固定)、ネットワーク不要- 再現:
new Socket(InetAddress.getLoopbackAddress(), 0, false)を含むAppを JDK 25 で実行すると、コンパイルは[removal]警告つきで通り、実行時にIllegalArgumentException: Socket constructor does not support creation of datagram socketsで終了コード 1 - 修正:
DatagramSocketに置き換えると、同じ JDK 25 で終了コード 0・シグネチャ消滅(ケースは最小化のためnew DatagramSocket(0, InetAddress.getLoopbackAddress())を使用。これはローカル bind の形で、シグネチャ消滅の証明には十分ですが、リモートに接続していた実コードの等価な置換は本文のとおりnew DatagramSocket()+connect(address, port)です)
再現から修正までは errfix の検証ハーネスが機械的に確認しています。版境界は、同じ new Socket(addr, port, false) が JDK 17 / 21 / 24 では非推奨警告つきでデータグラムソケットを作って実行が成功し、25 で IllegalArgumentException を投げることを実測して裏づけました(各版・挙動はケースの verification/probes.txt に記録)。加えて JDK 24 で、旧 Socket(address, port, false) が指定 address:port に接続済み(isConnected()=true・getPort()=接続先)で返ること、new DatagramSocket(port, address) がローカル bind・未接続になること、new DatagramSocket()+connect(address, port) が旧コンストラクタと同じリモート接続になることを実測して、置換の等価性を確認しました。stream=false が旧来データグラムソケットを作る指定であったこと、UDP には DatagramSocket を使うことは、Java の API ドキュメントに沿っています。