Sửa lỗi java.net.SocketTimeoutException: Read timed out trong Java HTTP Requests

intermediate Java2026-03-25| Java 8+, mọi hệ điều hành (Windows/Linux/macOS), HttpURLConnection / Apache HttpClient / OkHttp / Spring RestTemplate

Error Message

java.net.SocketTimeoutException: Read timed out
#java#network#http#timeout#socket#httpclient

TL;DR

HTTP client của bạn đã kết nối thành công đến server — nhưng server không trả về dữ liệu kịp thời. Mặc định, HttpURLConnection đặt read timeout là 0, tức là chờ mãi mãi. Hãy đặt timeout rõ ràng: 5–10 giây cho connect, 10–30 giây cho read. Thêm retry logic để xử lý mạng không ổn định.

Lỗi trông như thế nào

java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:171)
    at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
    ...

Kết nối TCP đã thành công — vậy DNS và firewall không có vấn đề gì. Nhưng không có phản hồi nào đến trong khoảng thời gian timeout. Nguyên nhân thường gặp: query phía backend chậm, payload lớn (ví dụ file CSV 50 MB), tắc nghẽn mạng, hoặc server âm thầm ngắt kết nối giữa chừng.

Cách sửa theo từng HTTP client

HttpURLConnection (thư viện chuẩn)

Mặc định không có read timeout — nó sẽ chặn mãi mãi. Luôn cấu hình rõ ràng cả hai:

URL url = new URL("https://api.example.com/data");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5_000);  // 5s để thiết lập kết nối TCP
conn.setReadTimeout(15_000);    // 15s để nhận dữ liệu sau khi kết nối
conn.setRequestMethod("GET");

try (InputStream in = conn.getInputStream()) {
    // đọc response
}

Apache HttpClient 5.x

RequestConfig config = RequestConfig.custom()
    .setConnectionRequestTimeout(Timeout.ofSeconds(5))
    .setConnectTimeout(Timeout.ofSeconds(5))
    .setResponseTimeout(Timeout.ofSeconds(15))  // đây là read timeout
    .build();

try (CloseableHttpClient client = HttpClients.custom()
        .setDefaultRequestConfig(config)
        .build()) {

    HttpGet request = new HttpGet("https://api.example.com/data");
    try (CloseableHttpResponse response = client.execute(request)) {
        // xử lý response
    }
}

Với Apache HttpClient 4.x, thay setResponseTimeout() bằng setSocketTimeout() — tên tham số đã thay đổi giữa các phiên bản chính.

OkHttp

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(5, TimeUnit.SECONDS)
    .readTimeout(15, TimeUnit.SECONDS)
    .writeTimeout(10, TimeUnit.SECONDS)
    .build();

Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .build();

try (Response response = client.newCall(request).execute()) {
    // xử lý response
}

Spring RestTemplate

SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5_000);
factory.setReadTimeout(15_000);

RestTemplate restTemplate = new RestTemplate(factory);
String result = restTemplate.getForObject("https://api.example.com/data", String.class);

Muốn dùng backend Apache HttpClient cho môi trường production? Cấu hình như một bean:

@Bean
public RestTemplate restTemplate() {
    HttpComponentsClientHttpRequestFactory factory =
        new HttpComponentsClientHttpRequestFactory();
    factory.setConnectTimeout(5_000);
    factory.setConnectionRequestTimeout(5_000);
    factory.setReadTimeout(15_000);
    return new RestTemplate(factory);
}

Spring WebClient (reactive)

HttpClient httpClient = HttpClient.create()
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5_000)
    .responseTimeout(Duration.ofSeconds(15));

WebClient webClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build();

Thêm retry logic cho các lỗi tạm thời

Một lần timeout không có nghĩa server đã chết. Mạng vốn hay có sự cố. Bọc lời gọi trong retry logic để những gián đoạn ngắn không hiện ra thành lỗi cho người dùng:

// Dùng Spring Retry
@Retryable(
    value = { SocketTimeoutException.class, ResourceAccessException.class },
    maxAttempts = 3,
    backoff = @Backoff(delay = 1000, multiplier = 2)
)
public String callApi() {
    return restTemplate.getForObject(url, String.class);
}

Không muốn dùng Spring Retry? Tự implement với exponential backoff:

int maxRetries = 3;
int delayMs = 1000;

for (int attempt = 1; attempt <= maxRetries; attempt++) {
    try {
        return callExternalApi();
    } catch (SocketTimeoutException e) {
        if (attempt == maxRetries) throw new RuntimeException("All retries failed", e);
        Thread.sleep(delayMs * attempt);
    }
}

Chọn giá trị timeout phù hợp

- **Connect timeout**: 3–10 giây. Một TCP handshake bình thường hoàn tất trong vài mili giây trên mạng nội bộ, dưới 1 giây qua internet.
- **Read timeout**: Tùy thuộc vào endpoint. REST API nhanh: 10–15 giây. Xuất dữ liệu lớn hoặc tải file: 60–300 giây.
- **Không bao giờ đặt read timeout là 0** trong môi trường production — một server bị treo sẽ giữ thread của bạn vô thời hạn.

Quy tắc nhanh: đặt read timeout bằng hai lần thời gian phản hồi tệ nhất dự kiến cho endpoint đó. Nếu một endpoint báo cáo thường phản hồi trong 8 giây, hãy đặt 16–20 giây.

Debug nguyên nhân gốc rễ

Tăng timeout chỉ là giải pháp tạm thời. Trước tiên, hãy tìm hiểu tại sao server chậm:

- Kiểm tra log phía server vào đúng thời điểm xảy ra timeout
- Tái hiện lỗi bên ngoài Java: `curl --max-time 15 https://api.example.com/data`
- Timeout xảy ra lẻ tẻ thường do vấn đề mạng. Xảy ra liên tục thường do hiệu năng server.
- Thêm timestamp vào log để xác định bước nào chậm

Đang dùng proxy doanh nghiệp hoặc VPN? Sự cố routing có thể gây ra tình trạng read stall âm thầm trông giống hệt server chậm. Công cụ Subnet Calculator tại toolcraft.app có thể giúp xác minh CIDR range và đường đi routing khi debug khả năng kết nối giữa các service.

Kiểm tra lại sau khi sửa

- Mô phỏng timeout: trỏ đến một endpoint chậm, hoặc thêm `Thread.sleep(20_000)` trong handler của test server
- Xác nhận exception được ném ra đúng sau timeout đã cấu hình — chính xác 15 giây, không phải 30
- Thực hiện request bình thường và xác nhận nó hoàn tất trong ngưỡng thời gian
- Kiểm tra log: `SocketTimeoutException` phải xuất hiện vào thời điểm dự đoán được, không phải sau một khoảng trễ ngẫu nhiên

Connection pool bị cạn kiệt (vấn đề liên quan)

Hãy tái sử dụng HTTP client — không bao giờ tạo mới CloseableHttpClient hay OkHttpClient cho mỗi request. Khi các timeout dồn lại mà không được xử lý, chúng sẽ làm cạn kiệt connection pool và gây ra lỗi dây chuyền trong toàn bộ service. Đặt giới hạn pool một cách rõ ràng:

// Apache HttpClient 5.x
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(100);
cm.setDefaultMaxPerRoute(20);

CloseableHttpClient client = HttpClients.custom()
    .setConnectionManager(cm)
    .setDefaultRequestConfig(config)
    .evictExpiredConnections()
    .build();

Related Error Notes