エラーの内容
Javaサーバーを起動すると、すぐに以下のエラーで落ちます:
Exception in thread "main" java.net.BindException: Address already in use: bind
at sun.nio.ch.Net.bind0(Native Method)
at sun.nio.ch.Net.bind(Net.java:461)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:223)
at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:134)
Spring Bootの場合は、少し読みやすいバナーとして表示されます:
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 8080 was already in use.
Action:
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.
サーバーが使いたいポートがすでに占有されています。OSは厳格な「1ポート1プロセス」のルールを強制しており、例外はありません。
原因
よくある原因はいくつかあります:
- サーバーの前のインスタンスが正常に終了せず、バックグラウンドでまだ動いている — これが最も多い原因です。
- 別のプロセスが先にポートを取得している:別のTomcat、2つ目のSpring Bootアプリ、またはデータベース(PostgreSQLのデフォルトは5432、MySQLは3306、Redisは6379)など。
- Dockerを使っていて、コンテナのポートマッピングがホスト側の何かと競合している。
- ソケットが
TIME_WAIT状態になっている。前のプロセスは終了しているが、カーネルが迷子パケットを拾うために最大60秒間そのポートを保持している。
対処法①:ポートを使用しているプロセスを見つけて終了する
Linux / macOS
まずポート8080で何が動いているか確認します:
# lsofを使う場合
lsof -i :8080
# ssを使う場合(モダンなLinuxではより高速)
ss -tlnp | grep 8080
# netstatを使う場合
netstat -tulnp | grep 8080
以下のような出力が得られます:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 12345 ubuntu 42u IPv6 123456 0t0 TCP *:8080 (LISTEN)
PID 12345が犯人です。強制終了します:
kill -9 12345
PIDの確認を省略したワンライナーを使いたい場合:
fuser -k 8080/tcp
Windows
netstat -ano | findstr :8080
最後の列からPIDを取得し、強制終了します:
taskkill /PID 12345 /F
PowerShellで2ステップをまとめることもできます:
Get-Process -Id (Get-NetTCPConnection -LocalPort 8080).OwningProcess | Stop-Process -Force
対処法②:サーバーのポートを変更する
他のプロセスを終了できない、またはしたくない場合は、アプリを別のポートに移動しましょう。
Spring Boot(application.properties)
server.port=8081
Spring Boot(application.yml)
server:
port: 8081
Spring Boot(コマンドライン — 設定ファイル変更不要)
java -jar myapp.jar --server.port=8081
組み込みTomcat(プログラムで設定)
Connector connector = new Connector();
connector.setPort(8081);
tomcat.getService().addConnector(connector);
通常のJava ServerSocket
// 特定の空きポートを指定する
ServerSocket serverSocket = new ServerSocket(8081);
// またはOSに任せる — 0を渡して割り当てられたポートを確認する
ServerSocket serverSocket = new ServerSocket(0);
int assignedPort = serverSocket.getLocalPort();
System.out.println("Server started on port: " + assignedPort);
ポート0はあまり使われていません。OSが空いているポートを選んでくれるので、競合が発生しません。
対処法③:SO_REUSEADDRを有効にする
ポートがTIME_WAIT状態で詰まっている場合は、SO_REUSEADDRが解決策です。bind()を呼び出す前に設定すると、ソケットは待機時間をスキップします:
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true); // bind()より前に設定する必要がある
serverSocket.bind(new InetSocketAddress(8080));
Netty、Tomcat、Jettyはこのフラグを自動的に設定します。ゼロからサーバーを構築している場合にのみ、自分で設定する必要があります。
Docker特有の対処法
Dockerで実行している場合、競合はコンテナ内ではなくホスト側で発生している可能性があります:
# ポート8080にマッピングされているコンテナを確認する
docker ps
lsof -i :8080
# 競合しているコンテナを停止する
docker stop <container_id>
# または別のホストポートにリマップする
docker run -p 8081:8080 myapp
最後のコマンドは、コンテナ内でアプリが8080でリッスンし続けます。ホスト側からは8081を通してアクセスするだけです。
修正の確認
再起動する前に、ポートが実際に空いていることを確認します:
# Linux/macOS
lsof -i :8080
# 出力なし = ポートは空き
# Windows
netstat -ano | findstr :8080
# 出力なし = ポートは空き
サーバーを起動し、バインド成功のメッセージ(Started Application in 2.4 secondsやServer started on port 8080など)が表示されれば完了です。
予防策
- 適切にシャットダウンする:SIGTERMまたはCtrl+Cを使用し、ターミナルを閉じるだけにしないこと。ターミナルを閉じるとJVMプロセスが孤立したまま残り、そのプロセスがポートを占有し続けます。アプリにシャットダウンフックがない場合は登録しましょう。
- テストではポート0を使用する:結合テストでハードコードされたポートにバインドすると、特に複数のジョブが並列実行されるCI環境でトラブルの元になります。
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)を使用して、Springに空きポートを選ばせましょう。 - 起動スクリプトで事前チェックを行う:JVMが起動する前に素早く失敗させましょう:``` lsof -i :8080 && echo "Port in use!" && exit 1
- **マイクロサービスにおけるサブネット・ネットワーク競合**:複数のサービスを実行していて、ポートではなく特定のIPでバインドエラーが発生している場合、アドレス範囲が重複している可能性があります。[ToolCraftのサブネット計算機](https://toolcraft.app/en/tools/developer/ip-subnet-calculator)でネットワーク割り当てを確認できます — ブラウザ上で完結し、データのアップロードは不要です。

