エラーについて
おそらく目にしたことがあるでしょう。新しいRESTエンドポイントやデータベース接続をテストしようとした際、Javaが大量のテキスト(スタックトレース)を投げつけてくることがあります。この特定の例外は、通常次のように表示されます。
java.net.ConnectException: Connection refused (Connection refused)
at java.base/sun.nio.ch.Net.connect0(Native Method)
at java.base/sun.nio.ch.Net.connect(Net.java:579)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:585)
at java.base/java.net.Socket.connect(Socket.java:633)
at java.base/java.net.Socket.<init>(Socket.java:507)
エラーの本当の意味
このエラーは非常に直接的です。クライアントは宛先サーバーに正常に到達しましたが、サーバー側が明示的に「拒否」したことを意味します。サーバーが単に無視する Connection Timeout (接続タイムアウト)とは異なり、Connection Refused (接続拒否)は、OSがTCP RST(リセット)パケットを返したことを示しています。
要するに、ドアはそこにありますが、鍵がかかっていて誰もいない状態です。
一般的な原因と解決策
1. ターゲットサービスが起動していない
これが最も一般的な原因です。ポート8080のマイクロサービスにアクセスしようとしているのに、mvn spring-boot:run を実行し忘れているようなケースです。プロセスがアクティブでない場合、OSは接続を渡す先がありません。
解決策: サービスが実際にリスニング状態にあるか確認してください。サーバー上で以下のコマンドを実行します。
# ポート8080でリスニングしているか確認 (Linux/macOS)
sudo lsof -i :8080
# またはWindowsでnetstatを使用
netstat -ano | findstr :8080
これらのコマンドの結果が空であれば、アプリケーションを起動してください。
2. ポートの不一致
複数のサービスを管理していると、ポートを混同しがちです。サーバーはポート 9000 で動作しているのに、クライアントがデフォルトの 8080 を探すようにハードコードされたままになっていることがあります。これは、application.properties の設定変更がフロントエンドに同期されていない場合によく起こります。
解決策: 設定ファイルを並べて開いてください。バックエンドの server.port が、クライアントコードの BASE_URL と正確に一致していることを確認します。
3. 「Localhost」の罠(バインドの問題)
これは、ローカルテストからDockerや本番環境に移行する開発者が直面する典型的な悩みです。サーバーが 127.0.0.1 にバインドされている場合、サーバー自身からの接続しか受け付けません。クライアントがサーバーのLAN IP(192.168.1.50 など)経由で接続しようとすると、OSはそれを拒否します。
解決策: サーバーが 0.0.0.0 にバインドするように設定します。これにより、アプリケーションは利用可能なすべてのネットワークインターフェースでリスニングするようになります。
// Java ServerSocketの例:すべてのインターフェースにバインド
ServerSocket server = new ServerSocket(8080, 50, InetAddress.getByName("0.0.0.0"));
Spring Bootでは、プロパティファイルに server.address=0.0.0.0 を追加するだけです。
4. ファイアウォールとクラウドのセキュリティグループ
サービスが完璧に動作していても、セキュリティレイヤーが邪魔をしていることがあります。多くのファイアウォールはパケットを黙って「ドロップ」し(タイムアウトの原因になる)、一部は「拒否(reject)」するように設定されています。これは、ローカルの ufw 設定やAWSのセキュリティグループでよく見られます。
解決策: ファイアウォールのルールを確認してください。Linuxサーバーでは、ポートを開放してすぐにテストできます。
# UFWでポート8080のトラフィックを許可
sudo ufw allow 8080/tcp
5. 接続バックログがいっぱい
高負荷時、サーバーが新しい接続の受け入れを停止することがあります。Javaの ServerSocket には、保留中の接続のためのキューであるデフォルトの「バックログ」があります。サーバーの処理が遅いためにこのキュー(通常はデフォルトで50)がいっぱいになると、OSは新しいリクエストを拒否し始めます。
解決策: バックログのサイズを大きくするか、より良い方法として、リクエスト処理ロジックを最適化してキューをより速く消化できるようにします。
解決策を確認する方法
ポートが開いていることを確認する前に、重いJavaアプリを再起動して時間を無駄にしないでください。まずは軽量なツールを使用して接続を調査します。
Netcat (nc) の使用:
nc -zv 192.168.1.50 8080
「Connection to 192.168.1.50 port 8080 [tcp/http] succeeded!」というメッセージが表示されれば、ネットワーク経路はようやく開通したことになります。
現場からの個人的なアドバイス
複雑なマイクロサービス群をデバッグしているとき、IP範囲の問題が「静かなる殺し屋」であることに気づくことがよくあります。私はこの サブネット計算機 を使用して、CIDRブロックと 0.0.0.0 のバインドがデプロイ先のネットワークで実際に理にかなっているかを再確認しています。
もう一点、catchブロックでは必ず ターゲットURI をログに記録してください。本番環境のログに Failed to connect to 127.0.0.1:8080 と表示されていれば、大幅な時間の節約になります。アプリが本番データベースではなく、自分自身と話そうとしていることがすぐにわかるからです。
try {
// ここに接続ロジックを記述
} catch (ConnectException e) {
logger.error("Connection refused to {}:{}", remoteHost, remotePort);
throw e;
}

