Error [Socket constructor does not support creation of datagram sockets]

Socket constructor does not support creation of datagram sockets の直し方(JDK 25 で古い Socket コンストラクタが例外になる)

FIX SUMMARY verified
Applies when
eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ffJDK 25 (Socket(InetAddress,int,boolean) with stream=false now throws IllegalArgumentException; deprecated-for-removal through 24)legacy code using new Socket(addr, port, false) to make a UDP/datagram socket, run on JDK 25 where the deprecated stream=false path throws IllegalArgumentException (JDK 17/21/24 only warned)

Verified: reproduced in eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff, then the Socket constructor does not support creation of datagram sockets signature was gone after the fix (exit 0).

長く使ってきた 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引数 streamfalse を渡す使い方が、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) のように引数を入れ替えるだけの置換は誤りです。それはローカルの portaddress に束縛するだけで、旧コンストラクタが行っていたリモートへの接続を再現しません(後述)。

なぜ起きるのか:stream=false は「データグラムソケットを作る」指定だった

Socket(InetAddress, int, boolean) の第3引数 stream は、true なら TCP(ストリーム)ソケット、false なら UDP(データグラム)ソケットを作る、という意味でした。JDK 1.1 以来の API で、false を渡すと、指定した address:port接続したデータグラムソケットが Socket 型で返ります(isConnected()truegetPort() がその接続先ポートを返します。実測で確認)。ローカルのポートは 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 で例外にします。

JDKnew Socket(addr, port, false) の挙動
17 / 21 / 24非推奨警告つきでデータグラムソケットを作る(実行は成功)
25IllegalArgumentException: 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)です。ここに旧コードのリモート portaddress を渡すと、リモートに接続する代わりにそのポートをローカルで 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)を呼んでいた箇所は DatagramSocketsend(DatagramPacket) / receive(DatagramPacket) に直します。stream=false で作っていたソケットは実体がデータグラムで、データグラムソケットはストリーム API を持たないため、その経路は以前の版でも実行時に失敗していたはずです。

切り分け(うまくいかないとき)

  • エラーが Unrecognized VM optionUnrecognized 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()truegetPort()=接続先)で返ること、new DatagramSocket(port, address) がローカル bind・未接続になること、new DatagramSocket()connect(address, port) が旧コンストラクタと同じリモート接続になることを実測して、置換の等価性を確認しました。stream=false が旧来データグラムソケットを作る指定であったこと、UDP には DatagramSocket を使うことは、Java の API ドキュメントに沿っています。

検証(machine-verified)

この修正は eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff のバージョン固定コンテナ内で再現し、修正後に Socket constructor does not support creation of datagram sockets のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs jvm/socket-datagram-constructor-jdk25
● reproduce Socket constructor does not support creation of datagram sockets present ✓
● apply fix exit 0
● re-run Socket constructor does not support creation of datagram sockets gone ✓
PASS verified · eclipse-temurin@sha256:68868d04fa9cfd5f5c6abec0b5cef86d8de2bf9c62c37c7d3e4f0f80f5cfd7ff · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。