3

Using the new java.net.http package released with JDK 11, an HttpRequest has been assembled with a deliberately low response timeout:

HttpRequest.Builder builder = HttpRequest.newBuilder(getEndpointUri());
addRequestHeaders(builder);
builder.POST(HttpRequest.BodyPublishers.ofString(rawXml));
builder.timeout(Duration.ofMillis(1));
HttpRequest httpRequest = builder.build();

The aim is to test that HttpTimeoutException outcomes are handled correctly, but unexpectedly this response timeout value is leading to an HttpConnectionTimeoutException, which is being caught by this code:

try {
    HttpResponse<InputStream> httpResponse = completableExchange.join();
} catch (CompletionException ce) {
    if (ce.getCause() instanceof HttpConnectTimeoutException) {
        System.out.println("Connection timeout occurred!");
    } else {
        throw ce;
    }
}

This means that a response timeout is causing the code to act as though a connection timeout has occurred. To the best of my understanding, the connection timeout and response timeout should be separate concepts, which should be possible to catch and handle separately.

The stack trace attached to the HttpConnectionTimeoutException looks like this:

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:1248)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:877)
Caused by: java.net.ConnectException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69)
    ... 2 more

Am I misunderstanding the timeout concepts? Does the HttpRequest timeout value simply provide an alternative to the default of the HttpClient timeout value? Is there a reliable way to catch connection and response timeout as distinct events?

For what it's worth, the Javadoc for HttpRequest.Builder.timeout(Duration) says the following:

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.

To make things confusing, HttpConnectionTimeoutException is a subclass of HttpTimeoutException so technically the contract of the timeout(Duration) method is being satisfied. But this seems unhelpful.

(Before you ask: yes, the value passed to HttpRequest.Builder.timeout(Duration) is the deciding factor in whether or not an exception is thrown. So the exception is not based on the connection timeout value being used to create the HttpClient instance.)

Bobulous
  • 12,967
  • 4
  • 37
  • 68
  • it appears as though you're forming your request a little differently to what I can see in the docs, have you tried modifying your request to match the likes published in the [Introduction to the Java HTTP Client](https://openjdk.java.net/groups/net/httpclient/intro.html) – Matt G Dec 21 '18 at 01:12
  • What significant differences do you see which might explain the odd exception behaviour? The timeout is deliberately being set low to test for request timeouts, and I need the custom headers and URL. And I'm using a `BodyPublisher` fed by a String because my data is in memory and not in a file. – Bobulous Dec 21 '18 at 21:53

1 Answers1

3

IIRC you will get a HttpConnectionTimeoutException if the connection is not connected at the time the timeout is raised, or if the connect timeout is raised before the connection has finished connecting.

When sending a request - the underlying connection might already be connected or not - depending on whether a suitable existing connection was found in the pool. The request timeout starts immediately - independently of the state of the underlying connection. If the underlying connection is not connected yet, and the request timeout expires before it gets connected, then you will get a HttpConnectionTimeoutException because the connection could not be connected within the time allocated for the response to the request to be delivered. You could see it as the request timeout clipping the connect timeout.

Do you have any specific use case in mind for distinguishing the two cases:

  1. HttpConnectionTimeoutException is raised because the connection could not be connected within the time specified by the connection timeout,
  2. HttpConnectionTimeoutException is raised because the request timeout expired before the connection could be connected?
daniel
  • 2,665
  • 1
  • 8
  • 18
  • I'm writing code to consume an API whose specification gives different instructions (regarding retries) depending upon whether the connection failed (indicating a problem with the API server) or the request failed (likely indicating a problem with the upstream service). If `java.net.http` starts the request timer even before the connection has been made, then that surely makes it difficult to separate the two different timeout concepts? One overlaps the other. – Bobulous Jan 18 '19 at 16:08
  • The `java.net.http` client will retry idempotent requests (GET, HEAD) once, by default. It will not retry POST. However, it may try to retry in case of ConnectException. – daniel Jan 18 '19 at 19:35
  • @daniel Any source for this? Can't find these information in the javadocs and this seems very unintuitive. – Shubham Feb 22 '22 at 13:06
  • 1
    I am afraid that's undocumented behavior. However - if a timeout occurs before the connection was established, shouldn't it be considered as a connect timeout? https://github.com/openjdk/jdk/blob/3cfffa4f8e5a0fff7f232130125c549f992b533b/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java#L565 – daniel Feb 24 '22 at 11:35
  • Not having a problem with that being called connection timeout, but it not being properly documented would probably cause a lot of confusion. – Shubham Mar 16 '22 at 12:45