A RestTemplate uses a ClientHttpRequestFactory to turn its operations into HTTP requests. To use Apache HttpClient 5 explicitly, create one reusable Apache client, pass it to HttpComponentsClientHttpRequestFactory, and build the RestTemplate with that factory. This gives the application control over connection pooling and timeouts while preserving Spring’s response conversion.
The example uses Spring Boot 4.1.2. An order service checks product availability through a separate inventory API. Many orders may call the same API at once, so the inventory client uses a bounded connection pool instead of building a new HTTP client for each request.
1. Add the HTTP Client Dependencies
The REST client starter supplies RestTemplateBuilder and message conversion. Add Apache httpclient5 for the underlying HTTP implementation. Spring Boot manages compatible dependency versions, so the POM does not pin an Apache version separately.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.2</version>
<relativePath/>
</parent>
<properties><java.version>17</java.version></properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
</dependencies>
Apache HttpClient 5 classes use the org.apache.hc.client5 package. Older examples that import org.apache.http describe HttpClient 4 and should not be pasted into this configuration.
2. Build a Shared Connection Pool
The inventory API responds to GET /inventory/{sku} with JSON such as {"sku":"SKU-42","available":true}. The order service sends many such calls to the same host. A PoolingHttpClientConnectionManager keeps reusable connections and limits how many can be leased at once.
@Bean
PoolingHttpClientConnectionManager connectionManager() {
return PoolingHttpClientConnectionManagerBuilder.create()
.setMaxConnTotal(20)
.setMaxConnPerRoute(10)
.setDefaultConnectionConfig(ConnectionConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(3))
.build())
.build();
}
@Bean(destroyMethod = "close")
CloseableHttpClient apacheHttpClient(PoolingHttpClientConnectionManager connectionManager) {
return HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
}
The total limit of 20 covers all destinations reached through this client. The per-route limit of 10 applies to a single route, such as the inventory service host. If more requests need a connection, they wait for one to be released. The ConnectionConfig sets the connection-establishment timeout for the pool’s routes. The CloseableHttpClient is an application bean, so Spring closes it at shutdown; it should not be constructed for every request.
3. Connect Apache HttpClient to RestTemplate
Spring’s HttpComponentsClientHttpRequestFactory adapts the Apache client to the ClientHttpRequestFactory interface used by RestTemplate. The configuration passes the same Apache client to every inventory request and sets two more timeout controls before building the template. For the builder’s other per-client settings, see the RestTemplateBuilder guide.
@Bean
RestTemplate inventoryRestTemplate(RestTemplateBuilder builder, CloseableHttpClient httpClient,
@Value("${inventory.base-uri:https://inventory.example}") String baseUri) {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectionRequestTimeout(Duration.ofSeconds(2));
factory.setReadTimeout(Duration.ofSeconds(5));
return builder.baseUri(baseUri)
.requestFactory(() -> factory)
.build();
}
The builder retains Spring Boot’s message converters and applies the base URI to relative paths. setConnectionRequestTimeout() limits how long a request waits to lease a connection from the pool. setReadTimeout() configures the response timeout. Neither should be described as a complete end-to-end deadline; DNS, connection setup, pool wait, server response, and body processing are distinct phases.
| Setting | Where it is configured | What it limits |
|---|---|---|
| Connection request timeout | Spring request factory | Waiting for a connection from the pool |
| Connect timeout | Apache ConnectionConfig | Establishing a new connection |
| Read timeout | Spring request factory | Waiting for a response |
Spring Framework 7’s request factory no longer has the older setConnectTimeout() setter. Setting the connection timeout in Apache’s ConnectionConfig keeps the code compatible with the current API.
4. Call the Inventory API
The application only needs a SKU and an availability flag. The InventoryClient passes the SKU as a URI variable, then asks RestTemplate to convert the JSON response into an InventoryAvailability record.
public record InventoryAvailability(String sku, boolean available) {}
public final class InventoryClient {
private final RestTemplate restTemplate;
public InventoryClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public InventoryAvailability availability(String sku) {
return restTemplate.getForObject("/inventory/{sku}", InventoryAvailability.class, sku);
}
}
For availability("SKU-42"), the client requests GET https://inventory.example/inventory/SKU-42. When the service returns the JSON shown above, the caller receives InventoryAvailability("SKU-42", true). This request uses the configured Apache client and pool through the request factory; the application code does not need Apache-specific calls.
5. Verify the Factory and Response
The repository test starts a local HTTP server and points the inventory client at its port. It verifies the selected request factory, the configured total connection limit, and the mapped response. A mock of RestTemplate alone would not prove that Apache executed an HTTP request.
var template = configuration.inventoryRestTemplate(new RestTemplateBuilder(), httpClient,
"http://localhost:" + server.getAddress().getPort());
assertThat(template.getRequestFactory())
.isInstanceOf(HttpComponentsClientHttpRequestFactory.class);
assertThat(pool.getMaxTotal()).isEqualTo(20);
assertThat(new InventoryClient(template).availability("SKU-42"))
.isEqualTo(new InventoryAvailability("SKU-42", true));
The test proves that the factory is Apache-backed and that a real local response becomes the expected Java record. It does not benchmark throughput or verify timeout expiry. Choose pool limits from measured concurrency and the remote service’s capacity; the values here illustrate the settings.
6. Explicit Configuration and Auto-Detection
Spring Boot can detect an HTTP request factory from the classpath. That is useful when the default choice is sufficient, but it does not express the pool limits and timeout decisions shown here. Explicitly supplying the factory makes the inventory client’s HTTP behavior reviewable in one configuration.
return builder.baseUri(baseUri)
.requestFactory(() -> factory)
.build();
For a new synchronous integration, Spring Framework recommends RestClient because RestTemplate is deprecated in Framework 7. The Spring REST client reference explains the current clients, and the Spring Boot REST client guide describes automatic HTTP-client selection. Existing RestTemplate clients can use the Apache configuration above while migration is planned.
7. FAQs
7.1. Is adding httpclient5 enough to configure the pool?
No. Having Apache on the classpath can affect HTTP-client detection, but it does not set this article’s total and per-route limits. The PoolingHttpClientConnectionManager and request-factory wiring apply those settings explicitly to the inventory client.
7.2. Should every request create a new Apache client?
No. The application shares a CloseableHttpClient backed by one connection manager so it can reuse connections. Spring closes that bean when the application context shuts down.
7.3. Does the read timeout cap the entire call?
No. Pool acquisition, connection setup and response waiting are separate phases. Configure the applicable controls independently and test the behavior against the service characteristics you actually have.
8. Conclusion
For an existing RestTemplate client that needs an Apache connection pool, build one long-lived CloseableHttpClient, pass it to HttpComponentsClientHttpRequestFactory, and use Boot’s builder for the inventory template. Keep pool limits and timeouts explicit, then verify the actual factory and HTTP result.
Happy Learning!
clientHttpRequestFactory.setHttpClient(httpClient); is wrong …
its require HttpClient object not a CloseableHttpClient httpClient;
attachment is Spring document screenshot.
clientHttpRequestFactory.setHttpClient(httpClient); is wrong …
its require HttpClient object not a CloseableHttpClient httpClient;
attachment is Spring document screenshot.
CloseableHttpClient implements HttpClient.
How would you externalize the value for fixedDelay. @Scheduled(fixedDelay = 10000) ?
Since it requires compile time constant… does spring have alternative schedules that will pass the value from external config at run time ?
Thanks for the tutorial.
is there way log the total number of connection , number of connection used , number of connection ideal..
When i configured same i got 404 while calling https and working for http. Could you please help me on that
You can set the SSL context as follows:
final String password = "password"; SSLContext sslContext = SSLContextBuilder .create() .loadTrustMaterial(ResourceUtils.getFile("classpath:keystore.p12"), password.toCharArray()) .build(); CloseableHttpClient client = HttpClients.custom() .setSSLContext(sslContext) .build();Greate article.
I used it to configure WebServiceTempate for SOAP WS.
It worked like a charm.
Thanks
Connection Keep alive is not needed if we are ok with PoolingConnectionManager’s 2000 ms default stale connections. Its explained in below link as default 2000ms and we can modify via constructor of PoolingConnManager
https://hc.apache.org/httpcomponents-client-4.5.x/current/httpclient/apidocs/org/apache/http/impl/conn/PoolingHttpClientConnectionManager.html#PoolingHttpClientConnectionManager(long,%20java.util.concurrent.TimeUnit)