Sửa lỗi java.net.BindException: Address already in use Khi Khởi Động Java Server

beginner Java2026-05-03| Java 8+, Spring Boot, Tomcat, Jetty — Linux, macOS, Windows

Error Message

java.net.BindException: Address already in use: bind
#java#networking#bindexception#server

Lỗi Gặp Phải

Bạn khởi động Java server. Nó chết ngay lập tức với thông báo:

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 hiển thị lỗi này theo dạng thân thiện hơn một chút:

***************************
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.

Port mà server của bạn muốn dùng đã bị chiếm. Hệ điều hành áp dụng quy tắc nghiêm ngặt một chủ sở hữu: mỗi port chỉ cho một process, không có ngoại lệ.

Nguyên Nhân

Có một vài nguyên nhân phổ biến gây ra điều này:

  • Một instance trước đó của server không thoát sạch và vẫn đang chạy nền — đây là thủ phạm số 1.
  • Thứ gì đó khác đã chiếm port trước: một Tomcat khác, một Spring Boot app thứ hai, hoặc thậm chí một database (PostgreSQL mặc định dùng 5432, MySQL dùng 3306, Redis dùng 6379).
  • Bạn đang dùng Docker và port mapping của container xung đột với thứ gì đó trên host.
  • Socket đang ở trạng thái TIME_WAIT. Process trước đã thoát, nhưng kernel đang giữ port trong tối đa 60 giây để bắt các gói tin lạc.

Cách Sửa: Tìm và Kill Process Đang Chiếm Port

Linux / macOS

Bắt đầu bằng cách xem thứ gì đang dùng port 8080:

# Dùng lsof
lsof -i :8080

# Dùng ss (nhanh hơn trên Linux hiện đại)
ss -tlnp | grep 8080

# Dùng netstat
netstat -tulnp | grep 8080

Bạn sẽ thấy kết quả tương tự như:

COMMAND   PID   USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
java     12345  ubuntu  42u  IPv6  123456      0t0  TCP *:8080 (LISTEN)

PID 12345 chính là thủ phạm. Kill nó đi:

kill -9 12345

Muốn dùng một lệnh duy nhất mà không cần tra PID?

fuser -k 8080/tcp

Windows

netstat -ano | findstr :8080

Lấy PID từ cột cuối cùng, rồi force-kill nó:

taskkill /PID 12345 /F

Hoặc bỏ qua hai bước này bằng PowerShell:

Get-Process -Id (Get-NetTCPConnection -LocalPort 8080).OwningProcess | Stop-Process -Force

Cách Sửa: Đổi Port Của Server

Không thể hoặc không muốn kill process kia? Chuyển app của bạn sang port khác.

Spring Boot (application.properties)

server.port=8081

Spring Boot (application.yml)

server:
  port: 8081

Spring Boot (dòng lệnh — không cần thay đổi config)

java -jar myapp.jar --server.port=8081

Embedded Tomcat (lập trình thủ công)

Connector connector = new Connector();
connector.setPort(8081);
tomcat.getService().addConnector(connector);

Plain Java ServerSocket

// Chỉ định một port rảnh cụ thể
ServerSocket serverSocket = new ServerSocket(8081);

// Hoặc để OS tự chọn — truyền 0 và hỏi xem được port nào
ServerSocket serverSocket = new ServerSocket(0);
int assignedPort = serverSocket.getLocalPort();
System.out.println("Server started on port: " + assignedPort);

Port 0 ít được dùng tới. OS tự chọn một port rảnh, không thể có xung đột.

Cách Sửa: Bật SO_REUSEADDR

Port bị kẹt ở trạng thái TIME_WAIT? SO_REUSEADDR là giải pháp. Đặt nó trước khi gọi bind() và socket sẽ bỏ qua thời gian chờ hoàn toàn:

ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);  // phải đặt TRƯỚC khi bind()
serverSocket.bind(new InetSocketAddress(8080));

Netty, Tomcat và Jetty đều tự động bật flag này. Bạn chỉ cần tự cấu hình nếu đang xây dựng một raw server từ đầu.

Sửa Lỗi Trên Docker

Đang chạy trong Docker? Xung đột có thể nằm ở host, không phải bên trong container:

# Xem những container nào đang map với port 8080
docker ps
lsof -i :8080

# Dừng container bị xung đột
docker stop <container_id>

# Hoặc đơn giản là map sang host port khác
docker run -p 8081:8080 myapp

Lệnh cuối giữ nguyên app lắng nghe trên port 8080 bên trong container. Host chỉ truy cập nó qua port 8081 từ bên ngoài.

Xác Nhận Đã Sửa Xong

Trước khi khởi động lại, hãy xác nhận port thực sự đã trống:

# Linux/macOS
lsof -i :8080
# Không có output = port đã rảnh

# Windows
netstat -ano | findstr :8080
# Không có output = port đã rảnh

Khởi động server của bạn và tìm thông báo bind thành công — đại loại như Started Application in 2.4 seconds hoặc Server started on port 8080. Nếu thấy thông báo đó, bạn đã xong.

Phòng Tránh

  • Tắt đúng cách: Dùng SIGTERM hoặc Ctrl+C — đừng chỉ đóng terminal. Đóng terminal có thể làm JVM process bị mồ côi, và process đó tiếp tục giữ port. Hãy đăng ký một shutdown hook nếu app của bạn chưa có.
  • Dùng port 0 trong test: Bind vào một port cố định trong integration test là tự rước họa vào thân, đặc biệt trong CI khi nhiều job chạy song song. Dùng @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) và để Spring tự chọn port rảnh.
  • Kiểm tra trước khi khởi động: Thất bại sớm trước khi JVM thậm chí khởi động:``` lsof -i :8080 && echo "Port in use!" && exit 1
  - **Xung đột subnet/mạng trong microservices**: Nếu bạn đang chạy nhiều service và gặp bind error trên IP cụ thể thay vì port, có thể bạn có các dải địa chỉ bị chồng lấp. [Subnet Calculator trên ToolCraft](https://toolcraft.app/en/tools/developer/ip-subnet-calculator) có thể giúp bạn kiểm tra phân bổ mạng — chạy hoàn toàn trên trình duyệt, không upload gì cả.

Related Error Notes