[Java] Java HTTP Client API timeout settings

5 minute read

Overview

  • Verify the operation at the time of connection regarding the timeout setting of HTTP Client API (java.net.http package) officially introduced from Java 11
  • There are two types of timeout values that can be set in HTTP Client API -Connection timeout: HttpClient.Builder.connectTimeout -Request timeout: HttpRequest.Builder.timeout

Consideration from verification results

  • Connection timeout Specify the allowable time for connection timeout in HttpClient.Builder.connectTimeout
  • Request timeout Specify the allowable time of the entire request (including connection timeout and read timeout) in HttpRequest.Builder.timeout

Information from official document

HttpClient.Builder.connectTimeout

HttpClient.Builder (Java SE 14 & JDK 14)

HttpClient.Builder connectTimeout(Duration duration)

Set the connection timeout period for this client. HttpClient::send throws HttpConnectTimeoutException when HttpClient::send throws HttpConnectTimeoutException and HttpClient::sendAsync completes exceptionally with HttpConnectTimeoutException when a new connection cannot be established within the specified duration .. This timeout period has no effect if there is no need to establish a new connection, for example if the connection can be reused from a previous request.

Parameters: duration-the duration to allow the underlying connection to be established

Returns: This builder

Exception: IllegalArgumentException-if the period is non-positive

HttpClient.Builder (Java SE 14 & JDK 14)

HttpClient.Builder connectTimeout(Duration duration)

Sets the connect timeout duration for this client. In the case where a new connection needs to be established, if the connection cannot be established within the given duration, then HttpClient::send throws an HttpConnectTimeoutException, or HttpClient::sendAsync completes exceptionally with an HttpConnectTimeoutException.If a new connection does not need to be established, for example if a connection can be reused from a previous request, then this timeout duration has no effect.

Parameters: duration-the duration to allow the underlying connection to be established

Returns: this builder

Throws: IllegalArgumentException-if the duration is non-positive

HttpRequest.Builder.timeout

HttpRequest.Builder (Java SE 14 & JDK 14)

HttpRequest.Builder timeout(Duration duration)

Set the timeout for this request. If no response is received within the specified timeout, HttpClient::send or HttpClient::sendAsync will throw an HttpTimeoutException exceptionally with an HttpTimeoutException. The effect of not setting a timeout is the same as setting an infinite period (persistent block).

Parameters: duration-timeout period

Returns: This builder

Exception: IllegalArgumentException-if the period is non-positive

HttpRequest.Builder (Java SE 14 & JDK 14)

HttpRequest.Builder timeout(Duration duration)

Sets a timeout for this request.If the response is not received within the specified timeout then an HttpTimeoutException is thrown from HttpClient::send or HttpClient::sendAsync completes exceptionally with an HttpTimeoutException.The effect of not setting a timeout is the same as setting an infinite Duration, ie block forever.

Parameters: duration-the timeout duration

Returns: this builder

Throws: IllegalArgumentException-if the duration is non-positive

Source code to verify the behavior

Save the following contents with the file name HttpClientTimeoutTest.java.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.LocalDateTime;

public class HttpClientTimeoutTest {

  public static void main(String[] args) throws Exception {

    // Test connection timeout (non-existent server, connection timeout 3 seconds)
    testTimeout("http://127.0.0.2:8000/", Duration.ofSeconds(3), null);
    Thread.sleep(1000L);

    // Read timeout test (slow response server, request timeout 3 seconds)
    testTimeout("http://127.0.0.1:8000/", null, Duration.ofSeconds(3));
    Thread.sleep(1000L);

    // Test the impact area of request timeout (non-existent server, connection timeout 5 seconds, request timeout 3 seconds)
    testTimeout("http://127.0.0.2:8000/", Duration.ofSeconds(5), Duration.ofSeconds(3));
    Thread.sleep(1000L);
  }

  /**
   * Timeout test.
   * @param url URL
   * @param connectTimeout Value to be set in HttpClient.Builder.connectTimeout
   * @param requestTimeout Value to be set in HttpRequest.Builder.timeout
   */
  private static void testTimeout(String url, Duration connectTimeout, Duration requestTimeout) {

    try {
      // check parameters
      System.out.println("URL: "+ url);
      System.out.println("ConnectTimeout: "+ connectTimeout);
      System.out.println("RequestTimeout: "+ requestTimeout);

      // build the HttpClient object
      HttpClient.Builder hcb = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1);
      if (connectTimeout != null) {
        hcb.connectTimeout(connectTimeout); // set connection timeout
      }
      HttpClient client = hcb.build();

      // construct an HttpRequest object
      HttpRequest.Builder hrb = HttpRequest.newBuilder(new URI(url)).GET();
      if (requestTimeout != null) {hrb.timeout(requestTimeout); // set request timeout
      }
      HttpRequest request = hrb.build();

      // check the timeout setting
      System.out.println("HttpClient.connectTimeout: "+ client.connectTimeout());
      System.out.println("HttpRequest.timeout: "+ request.timeout());

      // Throw an HTTP request
      System.out.println("HttpClient.send begins: "+ LocalDateTime.now());
      HttpResponse<String> res = client.send(request, HttpResponse.BodyHandlers.ofString());

    } catch (Exception e) {
      System.out.println("HttpClient.send failed: "+ LocalDateTime.now());
      e.printStackTrace();
    }
  }
}

Prepare HTTP server with slow response

Write an HTTP server in Ruby that returns a response 10 seconds after a request comes.

Save the following contents with the file name server.rb.

require'webrick'

server = WEBrick::HTTPServer.new({
  :Port => 8000,
  :HTTPVersion => WEBrick::HTTPVersion.new('1.1')
})

server.mount_proc('/') do |req, res|
  sleep(10) # sleep for 10 seconds
  res.status = 200
  res.body =''
end

Signal.trap('INT'){server.shutdown}
server.start

Execute the HTTP server program with the ruby command.

$ ruby server.rb
[2020-06-28 11:03:15] INFO WEBrick 1.6.0
[2020-06-28 11:03:15] INFO ruby 2.7.1 (2020-03-31) [x86_64-darwin19]
[2020-06-28 11:03:15] INFO WEBrick::HTTPServer#start: pid=47723 port=8000

Execution result

Execute HttpClientTimeoutTest.java with java command.

If you are migrating to Java 11, you can run it without compiling with javac. Reference: JEP 330: Launch Single-File Source-Code Programs

This verification environment: macOS Catalina + Java 14 (AdoptOpenJDK 14.0.1+7)

$ java HttpClientTimeoutTest.java
URL: http://127.0.0.2:8000/
ConnectTimeout: PT3S
RequestTimeout: null
HttpClient.connectTimeout: Optional[PT3S]
HttpRequest.timeout: Optional.empty
HttpClient.send begins: 2020-06-28T11:07:13.619689
HttpClient.send failed: 2020-06-28T11:07:16.703287
java.net.http.HttpConnectTimeoutException: HTTP connect timed out
at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:557)
at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59)
at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:13)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
at java.net.http/jdk.internal.net.http.MultiExchange.toTimeoutException(MultiExchange.java:508)
at java.net.http/jdk.internal.net.http.MultiExchange.getExceptionalCF(MultiExchange.java:455)
at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:382)
at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:930)
at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:907)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2152)
at java.net.http/jdk.internal.net.http.Http1Exchange.lambda$cancelImpl$9(Http1Exchange.java:482)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: java.net.ConnectException: HTTP connect timed out
at java.net.http/jdk.internal.net.http.MultiExchange.toTimeoutException(MultiExchange.java:509)
... 10 more
URL: http://127.0.0.1:8000/
ConnectTimeout: null
RequestTimeout: PT3S
HttpClient.connectTimeout: Optional.empty
HttpRequest.timeout: Optional[PT3S]
HttpClient.send begins: 2020-06-28T11:07:17.713737
HttpClient.send failed: 2020-06-28T11:07:20.720473
java.net.http.HttpTimeoutException: request timed out
at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:561)
at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59)
at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:17)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
 at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
 at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
URL: http://127.0.0.2:8000/
ConnectTimeout: PT5S
RequestTimeout: PT3S
HttpClient.connectTimeout: Optional[PT5S]
HttpRequest.timeout: Optional[PT3S]
HttpClient.send begins: 2020-06-28T11:07:21.729119
HttpClient.send failed: 2020-06-28T11:07:24.735766
java.net.http.HttpConnectTimeoutException: HTTP connect timed out
 at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:557)
 at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
 at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59)
 at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:21)
 at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.base/java.lang.reflect.Method.invoke(Method.java:564)
 at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
 at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
 at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
 at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68)
 at java.net.http/jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1259)
 at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:888)
Caused by: java.net.ConnectException: HTTP connect timed out
 at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69)
 ... 2 more

実行結果の検証

接続タイムアウトのテスト (存在しないサーバ、接続タイムアウト3秒)

  • 接続タイムアウト: HttpClient.Builder.connectTimeout に3秒を指定
  • リクエストタイムアウト: HttpRequest.Builder.timeout は無指定
  • 3秒で接続タイムアウトが発生
  • 例外 java.net.http.HttpConnectTimeoutException が発生

  • 例外チェーン
  • java.net.http.HttpConnectTimeoutException: HTTP connect timed out
  • Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
  • Caused by: java.net.ConnectException: HTTP connect timed out

読み取りタイムアウトのテスト (レスポンスが遅いサーバ、リクエストタイムアウト3秒)

  • 接続タイムアウト: HttpClient.Builder.connectTimeout は無指定
  • リクエストタイムアウト: HttpRequest.Builder.timeout に3秒を指定
  • 3秒でリクエストタイムアウトが発生
  • 例外 java.net.http.HttpTimeoutException が発生

  • 例外チェーン (原因例外は無し)
  • java.net.http.HttpTimeoutException: request timed out

リクエストタイムアウトの影響範囲のテスト (存在しないサーバ、接続タイムアウト5秒、リクエストタイムアウト3秒)

  • 接続タイムアウト: HttpClient.Builder.connectTimeout に5秒を指定
  • リクエストタイムアウト: HttpRequest.Builder.timeout に3秒を指定
  • 3秒で接続タイムアウトが発生
  • 接続タイムアウトに5秒を指定しているのにもかかわらず3秒で接続タイムアウトが発生している
  • リクエストタイムアウトに設定した3秒で接続タイムアウトが発生していると思われる
  • リクエストタイムアウトは接続タイムアウトの時間込みの値だと考えられる
  • 例外 java.net.http.HttpTimeoutException が発生

  • 例外チェーン
  • java.net.http.HttpConnectTimeoutException: HTTP connect timed out
  • Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
  • Caused by: java.net.ConnectException: HTTP connect timed out

参考資料

Tags:

Updated: