0

I want to configure a timeout of 3 seconds against https://httpbin.org/delay/5 which respond back after 5 second only. This is to make sure a service which takes more than three second is doing a timeout after three seconds. I have followed the documentation and written below code where the httpclient is not timed out after 3 seconds instead it takes between 5 to 6 seconds to timeout.

public class HttpClientTimeoutExamplePoolingHttpClientConnectionManagerBuilder {
public static void main(String[] args) {
    try {

        DefaultClientTlsStrategy tlsStrategy = new DefaultClientTlsStrategy(SSLContext.getDefault());


        final PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
                .setTlsSocketStrategy(tlsStrategy)
                .setDefaultSocketConfig(SocketConfig.custom()
                        .setSoTimeout(Timeout.of(3, TimeUnit.SECONDS))
                        .build())
                .setMaxConnPerRoute(20)
                .setMaxConnTotal(200)
                .build();

        final RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(Timeout.of(1, TimeUnit.SECONDS))
                .setConnectTimeout(Timeout.of(3, TimeUnit.SECONDS))
                .setResponseTimeout(Timeout.of(3, TimeUnit.SECONDS))
                .build();

        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .evictExpiredConnections()
                .build();

        // Create GET request
        final HttpGet httpGet = new HttpGet("https://httpbin.org/delay/5");
        httpGet.setConfig(requestConfig);

        // Log start time
        long startTime = System.currentTimeMillis();
        System.out.println("Executing GET request at: " + startTime);

        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                long endTime = System.currentTimeMillis();
                System.out.println("Response received after: " + (endTime - startTime) + "ms");
                System.out.println("Response status: " + response.getCode());
                System.out.println("Response body: " + EntityUtils.toString(response.getEntity()));
            } finally {
                response.close();
            }
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            System.out.println("Exception after: " + (endTime - startTime) + "ms");
            System.out.println("Exception type: " + e.getClass().getName());
            System.out.println("Exception message: " + e.getMessage());
            e.printStackTrace();
        } finally {
            httpClient.close();
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
}

Output:

app.verify.http.HttpClientTimeoutExamplePoolingHttpClientConnectionManagerBuilder.main()

Executing GET request at: 1740675604245

Exception after: 6575ms

Exception type: java.net.SocketTimeoutException

Exception message: Read timed out

I have tried different config (socket level, connection level, request level) but was not able achieve the timeout functionality.

Then I have changed the code to use PoolingHttpClientConnectionManager and Registry and everything started working

public class HttpClientTimeoutExamplePoolingHttpClientConnectionManager {

public static void main(String[] args) {
    try {

        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        connectionManager.setDefaultSocketConfig(org.apache.hc.core5.http.io.SocketConfig.custom()
                .setSoTimeout(Timeout.of(3, TimeUnit.SECONDS))
                .build());
        connectionManager.setMaxTotal(200);
        connectionManager.setDefaultMaxPerRoute(20);

        // Define request config with timeouts
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(Timeout.of(1, TimeUnit.SECONDS))
                .setResponseTimeout(Timeout.of(3, TimeUnit.SECONDS))
                .setConnectionRequestTimeout(Timeout.of(3, TimeUnit.SECONDS))
                .build();

        // Build the client with timeouts applied at connection manager level
        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .evictExpiredConnections()
                .build();

        // Create GET request
        final HttpGet httpGet = new HttpGet("https://httpbin.org/delay/5");
        httpGet.setConfig(requestConfig);

        // Log start time
        long startTime = System.currentTimeMillis();
        System.out.println("Executing GET request at: " + startTime);

        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                long endTime = System.currentTimeMillis();
                System.out.println("Response received after: " + (endTime - startTime) + "ms");
                System.out.println("Response status: " + response.getCode());
                System.out.println("Response body: " + EntityUtils.toString(response.getEntity()));
            } finally {
                response.close();
            }
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            System.out.println("Exception after: " + (endTime - startTime) + "ms");
            System.out.println("Exception type: " + e.getClass().getName());
            System.out.println("Exception message: " + e.getMessage());
            e.printStackTrace();
        } finally {
            httpClient.close();
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
}

Output: app.verify.http.HttpClientTimeoutExamplePoolingHttpClientConnectionManager.main()

Executing GET request at: 1740675734246

java.net.SocketTimeoutException: Read timed out

Exception after: 3540ms

Exception type: java.net.SocketTimeoutException

Exception message: Read timed out

The issue is ConnectionSocketFactory, PlainConnectionSocketFactory and SSLConnectionSocketFactory are deprecated, so any idea how to achieve timeout using non deprecated class? Or can someone correct me the wrong config is the first class? (Would be great if you can use the same code with 3 second timeout that hit 5 second delay API endpoint)

I have tried https://github.com/apache/httpcomponents-client/blob/5.4.x/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java code and invoked the same URL with 3 second timeout and the behavior is same where request is taking around 5 to 6 second to timeout when I have configured the timeout as 3 seconds,.

Thanks

4
  • I have given explanations to a very similar question recently. Overzealous moderators here get very upset when the same answer is posted multiple times, so you will have to find it yourself in the list of previous questions Commented Mar 3 at 13:23
  • @ok2c thanks for the response. I have posted this because I couldn’t find a solution based on the documentation, any AI bots or even in stackoverflow. When I say couldn’t I mean the I have tried different solutions suggested in all forums / AI tools and nothing worked for me. Appreciate your response to this answer. Commented Mar 4 at 21:21
  • HttpClient timeouts work as expected. You expectations are wrong, however. Commented Mar 5 at 10:03
  • Hi @ok2c, could you please give some more clarification around this? When I set readtimeout as 3 seconds, I am expecting the call to throw a timeout after three seconds so that the calling client can continue the execution assuming the API couldn’t respond back on time. Commented Mar 5 at 10:16

3 Answers 3

1

If you measure the duration of HTTP communication in this way, it is the sum of connection establishment, request and waiting for the response. This is also the reason why even in code example 2 the duration is over 3 seconds. I have tried code example 1 with the following properties:

.setResponseTimeout(Timeout.of(3, TimeUnit.SECONDS)) -> Exception after: 3639ms
.setResponseTimeout(Timeout.of(2, TimeUnit.SECONDS)) -> Exception after: 2639ms
.setResponseTimeout(Timeout.of(7, TimeUnit.SECONDS)) -> Response received after: 5883ms

and it behaves as expected. Therefore, I assume that the 6 seconds you've reported are due to the connection setup and not the “non-functioning” of the response timeout.

So if you want to limit the total duration to a maximum of 3 seconds, I recommend limiting the call with a thread timeout instead, or using a tool like Awaitility.

Sign up to request clarification or add additional context in comments.

2 Comments

Hi @Arno Thanks for the response. Are you saying the first code works for you when you set timeout as 3 seconds and the connection will terminate in 3 seconds
yes, as you can see in my results. .setResponseTimeout(Timeout.of(3, TimeUnit.SECONDS)) -> Exception after: 3639ms .639 seconds to set up connection and 3 seconds until repsonse timeout.
1

Timeouts work as expected but your expectations are wrong.

  1. The socket timeout is NOT a deadline. It represents a maximum period of inactivity between consecutive i/o events or operations. For example, if the client endpoint sets the socket timeout to 5 seconds and the opposite endpoints sends a packet every 4 seconds, the message exchange will never time out.

  2. The async message transport used by async HttpClient has a timeout precision of one seconds by default. In some cases, for instance under load, the timeout precision may be off by several seconds. One can increase the precision of timeout events at the cost of greater CPU utilization.

Comments

0

You can go with http-request which built on top of Apache HTTP client5 to avoid write a lot of code, response parsing, response closing and exception handling.

var client = new ClientBuilder()
        .setDefaultMaxPoolSizePerRoute(20)
        .setMaxPoolSize(200)
        .setResponseTimeout(Timeout.ofSeconds(3))
        .setConnectTimeout(Timeout.ofSeconds(3))
        .setConnectionRequestTimeout(Timeout.ofSeconds(1))
        .setSocketTimeout(Timeout.ofSeconds(3))
        //other configs
        .build();

var httpRequest = HttpRequestBuilder.create(
                client
        )
        .build();
long startTime = System.currentTimeMillis();
httpRequest.target("https://httpbin.org/delay/5")
//          .setRequestConfig(requestConfig) //this can override the default configs specified in client if the client are shared.
        .get(String.class)
         //instead of under code you can just enable logging of com.jsunsoft.http and see exception and time of execution.
        .ifSuccess(rh -> {
            System.out.println("Response received after: " + (System.currentTimeMillis() - startTime) + "ms");
            System.out.println("Response status: " + rh.getCode());
            System.out.println("Response body: " + rh.orElse(null));
        }).otherwise(rh -> {
            System.out.println("Error after: " + (System.currentTimeMillis() - startTime) + "ms");
            System.out.println("Error message: " + rh.getErrorText());
        });

If you don't want to got with http-request to solve your mentioned deprecation see how configuration done here ClientBuilder

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.