How do you configure the underlying transport (connection/read timeouts, connection pooling) for RestClient via ClientHttpRequestFactory?
answer
- RestClient.builder().requestFactory(factory)
- Simple(JDK URLConnection) / JdkClient / Apache HttpComponents / Jetty / Reactor
- connect vs read vs connection-request timeout
- Apache HttpClient5 = pooling + fine timeouts
- Boot: spring.http.client.* + inject the Builder bean
basics
~10 sSet a ClientHttpRequestFactory on the builder: RestClient.builder().requestFactory(factory).build(). Use e.g. JdkClientHttpRequestFactory, HttpComponentsClientHttpRequestFactory, or SimpleClientHttpRequestFactory, and configure connect/read timeouts (and pooling) on that factory.
solid answer
~40 sRestClient delegates the actual HTTP transport to a ClientHttpRequestFactory, which you supply via .requestFactory(...) on the builder. Which factory you pick determines the client library and its capabilities: SimpleClientHttpRequestFactory (JDK HttpURLConnection, basic), JdkClientHttpRequestFactory (java.net.http.HttpClient, HTTP/2), HttpComponentsClientHttpRequestFactory (Apache HttpClient 5 — connection pooling, fine-grained timeouts), JettyClientHttpRequestFactory, or ReactorClientHttpRequestFactory (Netty). You set connectTimeout and readTimeout on the factory (Apache/Jetty/Reactor also expose pooling and more). In Spring Boot you can instead configure a ClientHttpRequestFactorySettings / ClientHttpRequestFactoryBuilder or rely on spring.http.client.* properties, and inject a preconfigured factory. Choosing and pooling the factory matters for throughput, resource limits, and resilience; the default (no pooling on the simple/JDK factories) can bottleneck under load.
code
java · 22 lines@Bean
ClientHttpRequestFactory pooledFactory() {
var cm = PoolingHttpClientConnectionManagerBuilder.create()
.setMaxConnTotal(200)
.setMaxConnPerRoute(50)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
var f = new HttpComponentsClientHttpRequestFactory(httpClient);
f.setConnectTimeout(2000); // TCP+TLS establish
f.setConnectionRequestTimeout(1000); // lease from pool
return f;
}
@Bean
RestClient upstreamClient(ClientHttpRequestFactory pooledFactory) {
return RestClient.builder()
.baseUrl("https://api.example.com")
.requestFactory(pooledFactory)
.build();
}go deeper
Know timeouts are set on a ClientHttpRequestFactory passed to the builder.
Name the factory implementations and set connect/read timeouts; know Apache adds pooling.
Distinguish the three timeout types, configure pooling/per-route limits, and understand Boot's factory auto-config and how overriding it can drop defaults.
Set org-wide outbound-call standards: mandatory timeouts, pooled Apache/Jetty transport, per-route budgets, and resilience (retries/circuit breakers/observability) wired into a shared client builder.
## The transport abstraction: `ClientHttpRequestFactory` RestClient does not open sockets itself. It asks a **`ClientHttpRequestFactory`** to create a `ClientHttpRequest`, which executes over some HTTP library. You choose the implementation on the builder: ```java RestClient client = RestClient.builder() .requestFactory(myFactory) .build(); ``` If you don't set one, Spring picks a default (detecting Apache/Jetty/Reactor on the classpath, else the JDK client — a `JdkClientHttpRequestFactory` — else the simple one). ## The implementations | Factory | Underlying client | Notes | |---|---|---| | `SimpleClientHttpRequestFactory` | JDK `HttpURLConnection` | No pooling; `setConnectTimeout`, `setReadTimeout` (ms). Minimal. | | `JdkClientHttpRequestFactory` | `java.net.http.HttpClient` (JDK 11+) | HTTP/2; wrap a preconfigured `HttpClient`; read timeout per request. | | `HttpComponentsClientHttpRequestFactory` | Apache HttpClient 5 | **Connection pooling**, fine-grained timeouts (connect, socket, connection-request), retries. Common production choice. | | `JettyClientHttpRequestFactory` | Jetty `HttpClient` | Pooling, HTTP/2. | | `ReactorClientHttpRequestFactory` | Reactor Netty | Even in a blocking client; pooling via Netty. | ## Setting timeouts **Simple / JDK factories:** ```java SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory(); f.setConnectTimeout(2000); // ms to establish TCP connection f.setReadTimeout(5000); // ms waiting for response data RestClient c = RestClient.builder().requestFactory(f).build(); ``` **Apache HttpClient 5 (pooling + timeouts):** ```java PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() .setMaxConnTotal(200) .setMaxConnPerRoute(50) .build(); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(cm) .build(); HttpComponentsClientHttpRequestFactory f = new HttpComponentsClientHttpRequestFactory(httpClient); f.setConnectTimeout(2000); f.setConnectionRequestTimeout(1000); // wait for a pooled connection RestClient c = RestClient.builder().requestFactory(f).build(); ``` Timeout meanings: - **connect timeout** — max time to establish the TCP (and TLS) connection. - **read/socket timeout** — max idle time waiting for response bytes. - **connection-request timeout** (pooled clients) — max time to lease a connection from the pool; protects against pool exhaustion. ## Spring Boot conveniences Boot lets you avoid hand-wiring: - Properties: `spring.http.client.connect-timeout`, `spring.http.client.read-timeout`, `spring.http.client.factory` (jdk/http-components/jetty/reactor/simple). - A `ClientHttpRequestFactorySettings` / `ClientHttpRequestFactoryBuilder` (Boot 3.4+) you can inject and apply, or customize the auto-configured `RestClient.Builder` bean. - Inject the shared `RestClient.Builder` bean and just `.build()` your client so it inherits Boot's configured factory, converters, and observability. ## Why it matters (when to tune) - **No timeouts = hung threads.** The simple/JDK defaults may be effectively infinite; always set connect + read timeouts for outbound calls, or a slow upstream ties up servlet threads. - **Pooling** with Apache/Jetty/Reactor is essential for high-throughput services; the simple factory opens a fresh connection each time. - **Per-route limits** prevent one upstream from starving connections to others. ## Gotchas - `SimpleClientHttpRequestFactory` has **no connection pool** — fine for low volume, a bottleneck under load. - Setting a factory on the builder replaces Boot's configured one, so you may lose Boot's default timeouts/observability — prefer customizing the injected builder. - Timeouts are in **milliseconds** (or `Duration` on newer setters); a value of 0 often means infinite. - The request factory is shared/reused; build it once as a bean, don't create one per request.
- What's the difference between connect timeout, read timeout, and connection-request timeout?Connect = time to establish the TCP/TLS connection; read/socket = max idle wait for response bytes; connection-request = time to lease a pooled connection before giving up (guards against pool exhaustion).
- Why prefer Apache HttpClient 5 over SimpleClientHttpRequestFactory in production?Apache provides connection pooling, per-route limits, and fine-grained timeouts/retries; the simple factory has no pool and opens a new connection per call, bottlenecking under load.
- In Spring Boot, how can you set timeouts without hand-building a factory?Use properties like spring.http.client.connect-timeout/read-timeout (or a ClientHttpRequestFactorySettings/Builder) and build clients from the injected RestClient.Builder bean so they inherit that configuration.
saying these in an interview costs you the question
- Not setting any timeouts, risking hung servlet threads on slow upstreams
- Assuming SimpleClientHttpRequestFactory pools connections
- Creating a new request factory per request instead of reusing a bean
- Thinking RestClient opens sockets directly rather than via ClientHttpRequestFactory