In this post, I would like to explain three ways of implementing a rest client in Spring boot / Java. Using WebFlux Webclient, native RestTemplate and Apache Client.
The simplest way is the native RestTemplate provided by the Spring Framework. It is blocking, and for each request, the connection is closed afterward. There is no connection pool or connection manager, which makes the requests costly and inefficient.
The settings here are the connection timeout and the read timeout. This means that the connection timeout defines how long the client waits to establish a connection, while the read timeout defines how long the client waits for a response after the connection has been established.
The more common approach is to use the Apache HttpClient dependency, which provides additional configuration options such as max-total, max-per-route, and connection-request-timeout-ms for more robust connection management and better performance. It also supports connection pooling, which reduces the overhead of creating a new connection for every request and improves the efficiency and scalability of the application.
max-total defines: the maximum number of total connections that can exist in the connection pool.
max-per-route: defines the maximum number of connections allowed for a specific target host or route.
connection-request-timeout-ms: defines how long the client waits to obtain a connection from the connection pool before throwing a timeout exception.
The third option is the reactive WebClient, which comes from Spring WebFlux. Unlike RestTemplate, it is non-blocking and asynchronous. This means the application does not wait for the response in a blocking way. Instead, it can continue working on other tasks and gets notified when the response is ready.
WebClient provides better scalability and resource usage, especially in applications with many concurrent requests. It is commonly used in modern microservice architectures and high-performance systems.
Some important properties and concepts are:
- connect-timeout: defines how long the client waits to establish a connection.
- response-timeout: defines how long the client waits for the server response.
max-in-memory-size: defines the maximum size of data stored in memory while processing a response. - Connection pooling is supported through Reactor Netty, which improves performance by reusing existing connections.
- Non-blocking I/O allows threads to handle multiple requests simultaneously instead of waiting for one request to finish.
It gives a quick response to the caller and notifies or calls back the application when the server response is ready. This makes the system more efficient and scalable under high load.
Here an example application yaml file for the above rest clients:
server: port: 8080rest-client: config: connection-timeout-ms: 1000 read-timeout-ms: 5000 apache: max-total: 100 max-per-route: 20 connection-request-timeout-ms: 500 webclient: connect-timeout-ms: 1000 response-timeout-ms: 5000 max-in-memory-size: 2097152
Java Cofig for the Rest Template Client:
package ch.dabbaghi.rest;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.client.SimpleClientHttpRequestFactory;import org.springframework.web.client.RestTemplate;@Configurationpublic class RestTemplateConfig {@Value("${rest-client.config.connection-timeout-ms}")private int connectionTimeout;@Value("${rest-client.config.read-timeout-ms}")private int readTimeout;@Beanpublic RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(connectionTimeout); factory.setReadTimeout(readTimeout); return new RestTemplate(factory); }}
Java Config for the Apache Htttp Client:
package ch.dabbaghi.rest;import org.apache.hc.client5.http.classic.CloseableHttpClient;import org.apache.hc.client5.http.impl.classic.HttpClients;import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;import org.apache.hc.client5.http.config.RequestConfig;import org.apache.hc.core5.util.Timeout;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;import org.springframework.web.client.RestTemplate;import java.util.concurrent.TimeUnit;@Configurationpublic class ApacheHttpClientConfig { @Value("${rest-client.config.connection-timeout-ms}") private int connectionTimeout; @Value("${rest-client.config.read-timeout-ms}") private int readTimeout; @Value("${rest-client.apache.max-total}") private int maxTotal; @Value("${rest-client.apache.max-per-route}") private int maxPerRoute; @Value("${rest-client.apache.connection-request-timeout-ms}") private int connectionRequestTimeoutMs; @Bean public RestTemplate apacheRestTemplate() { PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(maxTotal); connectionManager.setDefaultMaxPerRoute(maxPerRoute); RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout)) .setResponseTimeout(Timeout.ofMilliseconds(readTimeout)) .setConnectionRequestTimeout(Timeout.ofMilliseconds(connectionRequestTimeoutMs)) .build(); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(connectionManager) .setDefaultRequestConfig(requestConfig) .evictIdleConnections(Timeout.ofSeconds(30)) .build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); return new RestTemplate(factory); }}
Reactive Spring WebFlux Rest Client:
package ch.dabbaghi.rest;import io.netty.channel.ChannelOption;import reactor.netty.http.client.HttpClient;import java.time.Duration;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.client.reactive.ReactorClientHttpConnector;import org.springframework.web.reactive.function.client.WebClient;@Configurationpublic class WebClientConfig { @Value("${rest-client.config.connection-timeout-ms}") private int connectionTimeout; @Value("${rest-client.config.read-timeout-ms}") private int responseTimeout; @Value("${rest-client.webclient.max-in-memory-size}") private int maxInMemorySize; @Bean public WebClient webClient() { HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout) .responseTimeout(Duration.ofMillis(responseTimeout)); return WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .codecs(configurer -> configurer.defaultCodecs() .maxInMemorySize(maxInMemorySize)) .build(); }}

Hinterlasse einen Kommentar