How do you make RestTemplate production-ready regarding timeouts, connection pooling, and thread-safety?
answer
- default factory: no timeout, no pool, no PATCH
- set connect + read (+ connection-request) timeouts
- swap to HttpComponents/JDK factory; maxTotal + per-route
- configured RestTemplate is thread-safe -> singleton, never mutate
- add Resilience4j retry/CB + Micrometer metrics
basics
~10 sAlways set connect and read timeouts, back it with a pooled ClientHttpRequestFactory (e.g. Apache HttpClient) instead of the default one-connection-per-call factory, and reuse one configured, thread-safe RestTemplate as a singleton.
solid answer
~40 sA production RestTemplate needs three things. First, explicit timeouts: connect timeout (time to establish the socket) and read timeout (time waiting for response bytes) — set via RestTemplateBuilder; the default is infinite, which can hang threads and exhaust the pool. Second, a proper connection pool: the default SimpleClientHttpRequestFactory over HttpURLConnection doesn't pool and doesn't support PATCH, so back it with HttpComponentsClientHttpRequestFactory (Apache HttpClient with a PoolingHttpClientConnectionManager, sized for your concurrency) or the JDK HttpClient factory. Third, treat it as an immutable singleton: a fully-configured RestTemplate is thread-safe, so build one bean and share it — never mutate its converters/interceptors/factory after startup or construct it per request. Add resilience (retries, circuit breaker via Resilience4j) and observability (Micrometer metrics/tracing) around it, and set per-route pool limits so one slow dependency can't starve others.
code
java · 17 lines@Bean
RestTemplate pooledRestTemplate(RestTemplateBuilder builder) {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(100);
cm.setDefaultMaxPerRoute(20);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
// build one immutable, thread-safe bean; Micrometer auto-instruments it
return builder
.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient))
.connectTimeout(Duration.ofSeconds(2))
.readTimeout(Duration.ofSeconds(5))
.build();
}go deeper
Know you should set connect and read timeouts.
Swap in a pooled Apache/JDK HttpClient factory and reuse a single bean.
Size the pool with per-route limits, use the connection-request timeout, and add retries/metrics.
Define the org-wide resilient client blueprint: timeouts, per-route pooling to prevent cascading failure, idempotency-aware retries, circuit breakers, and Micrometer observability, delivered as a shared bean/starter.
## Why defaults are dangerous Out of the box `new RestTemplate()` uses `SimpleClientHttpRequestFactory`, which: - has **no timeouts** (both connect and read are effectively infinite), - opens a **fresh connection per request** via `HttpURLConnection` (no pooling / keep-alive reuse), - does **not** support `PATCH`. Under load or against a slow dependency this leaks threads (each blocked on an infinite read) and wastes connections. ## 1. Timeouts Two distinct timeouts: - **connect timeout** — max time to establish the TCP/TLS connection. - **read (socket) timeout** — max idle time waiting for response data once connected. Set both explicitly: ```java builder.connectTimeout(Duration.ofSeconds(2)) .readTimeout(Duration.ofSeconds(5)) .build(); ``` With Apache HttpClient there's also a **connection-request timeout** — time to lease a connection *from the pool*; important because pool exhaustion otherwise blocks callers indefinitely. ## 2. Connection pooling — swap the factory Replace the default factory with a pooled one: ```java PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(100); // total connections cm.setDefaultMaxPerRoute(20); // per target host CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(cm) .build(); ClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); ``` `maxTotal` caps overall connections; `defaultMaxPerRoute` caps per host so a single slow host can't consume the whole pool. The JDK 11+ `HttpClient` factory (`JdkClientHttpRequestFactory`) is a dependency-free alternative that also pools and supports PATCH. ## 3. Thread-safety and lifecycle A **fully-configured** RestTemplate is **thread-safe** and designed for concurrent use — *provided you don't mutate it after startup*. The invariants: - Build **one** bean (via `RestTemplateBuilder`) and inject it everywhere. - Do **not** add/remove interceptors, converters, or swap the factory at request time — those lists aren't safe to mutate concurrently. - Never `new RestTemplate()` per call — constructing (especially the pool) is expensive and defeats reuse. If different call sites need different config (e.g. different base URLs, auth, timeouts), create **multiple distinct beans**, each immutable. ## 4. Resilience and observability - **Retries**: idempotent GETs can retry on timeout; wrap with Spring Retry or Resilience4j. Don't blindly retry non-idempotent POSTs. - **Circuit breaker / bulkhead**: Resilience4j to fail fast when a dependency is down and isolate thread/connection usage per dependency. - **Metrics/tracing**: Spring Boot auto-instruments RestTemplate beans built from the builder with **Micrometer** (`http.client.requests` timers) and distributed tracing — another reason to use the builder rather than `new`. ## 5. Sizing guidance Pool size ≈ expected concurrent in-flight requests to that dependency. Per-route limits prevent one dependency's latency from starving connections needed for others (a classic cascading-failure vector). Pair with the connection-request timeout so waiting-for-a-connection also fails fast. ## Summary checklist Explicit connect/read (+ connection-request) timeouts • pooled factory (Apache or JDK HttpClient) with maxTotal + per-route limits • single immutable bean via builder, never mutated or re-created • retries only where idempotent • circuit breaker + Micrometer metrics.
- Why is leaving the read timeout at its default especially dangerous under load?The default is infinite. A slow/hung dependency keeps each calling thread blocked forever; with pooled connections and a bounded thread pool this cascades into pool and thread exhaustion, taking down unrelated endpoints.
- Two teams need RestTemplates with different auth and base URLs. Do you mutate one shared instance per call?No — mutating shared converters/interceptors at runtime isn't thread-safe. Create two separate immutable beans, each configured once, and inject the right one.
- What does per-route (defaultMaxPerRoute) protect against that maxTotal alone doesn't?It caps connections to a single host, so one slow dependency can't consume the entire pool and starve calls to healthy dependencies — preventing cascading failure.
saying these in an interview costs you the question
- Assuming RestTemplate has sensible default timeouts (it has none)
- Thinking the default factory pools connections
- Mutating a shared RestTemplate's interceptors/converters at request time
- Creating a new pooled RestTemplate per request
- Retrying non-idempotent POSTs blindly on timeout