spring-bootjettyspring-restclient

RestClient property spring.http.client.connect-timeout only seem to work up to 30 seconds


I'm using Spring Boot's RestClient and configuring the timeout via the property:

spring.http.client.connect-timeout=30s

When I execute a call to a stub server that intentionally delays the response for 50 seconds, I get the following exception:

org.springframework.web.client.ResourceAccessException: I/O error on POST request 
for "http://localhost:8080/endpoint": Total timeout 30000 ms elapsed
...
Caused by: java.util.concurrent.TimeoutException: Total timeout 30000 ms elapsed
    at org.eclipse.jetty.client.transport.HttpConnection$RequestTimeouts.onExpired(HttpConnection.java:347)
    at org.eclipse.jetty.client.transport.HttpConnection$RequestTimeouts.onExpired(HttpConnection.java:325)
    at org.eclipse.jetty.io.CyclicTimeouts.iterate(CyclicTimeouts.java:113)
    at org.eclipse.jetty.io.CyclicTimeouts$Timeouts.onTimeoutExpired(CyclicTimeouts.java:206)
    at org.eclipse.jetty.io.CyclicTimeout$Wakeup.run(CyclicTimeout.java:294)

However, if I set the timeout higher than 30 seconds (e.g., 60s), I get a different exception related to an idle timeout:

org.springframework.web.client.ResourceAccessException: I/O error on POST request 
for "http://localhost:8080/endpoint": Idle timeout expired: 30001/30000 ms
...
Caused by: java.util.concurrent.TimeoutException: Idle timeout expired: 30001/30000 ms
    at org.eclipse.jetty.io.IdleTimeout.checkIdleTimeout(IdleTimeout.java:167)
    at org.eclipse.jetty.io.IdleTimeout.idleCheck(IdleTimeout.java:113)

I know this can be circumvented by programmatically creating a ClientHttpRequestFactory bean:

@Bean
public ClientHttpRequestFactory getClientHttpRequestFactory(
    @Value(...) Duration connectTimeout,
    @Value(...) Duration readTimeout) {
  var factory = new SimpleClientHttpRequestFactory();
  factory.setConnectTimeout(connectTimeout);
  factory.setReadTimeout(readTimeout);
  return factory;
}

But my question is: Why does the property-based timeout configuration for spring.http.client.connect-timeout effectively cap at 30 seconds? Why can't it be configured higher without running into idle timeout exceptions?

Is there some internal limit, or perhaps multiple overlapping timeout settings in the underlying HTTP client (Jetty) that cause this behavior?


Solution

  • spring.http.client.connect-timeout sounds like it’s just the connection timeout, but it’s often also applied as a total timeout or request timeout internally by Jetty's client. This means that if your call takes longer than 30 seconds total (including waiting for data), Jetty aborts.

    When you increase the connect timeout beyond 30s (say, 60s), Jetty's idle timeout kicks in if no data is received during the wait. The idle timeout in Jetty defaults to 30 seconds (or some value close to that), so you get an idle timeout error instead.

    On your case:

    1. When you set spring.http.client.connect-timeout=30s, Jetty applies a request timeout of 30 seconds internally.

    2. When the server delays 50 seconds, Jetty cancels because the total timeout (request timeout) is reached.

    3. If you increase to 60 seconds, the idle timeout (default 30s) triggers because Jetty expects some data or progress before that.

    4. Since Spring Boot doesn’t expose configuring Jetty’s idle timeout via that property, you get an idle timeout exception.