skip to content

How do you make RestTemplate production-ready regarding timeouts, connection pooling, and thread-safety?

level: principalimportance: should knowfreq 40%

answer

  1. default factory: no timeout, no pool, no PATCH
  2. set connect + read (+ connection-request) timeouts
  3. swap to HttpComponents/JDK factory; maxTotal + per-route
  4. configured RestTemplate is thread-safe -> singleton, never mutate
  5. add Resilience4j retry/CB + Micrometer metrics

basics

~10 s

Always 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 s

A 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
java
@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

for a junior

Know you should set connect and read timeouts.

for a middle

Swap in a pooled Apache/JDK HttpClient factory and reuse a single bean.

for a senior

Size the pool with per-route limits, use the connection-request timeout, and add retries/metrics.

for a principal

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

context