Spring RestTemplate — why the set timeout does not work
Have you set timeouts for the restTemplate and your requests are still living much longer than they should? Well, there are more timeouts than you think (sometimes). The “sometimes” here is the tricky part of the problem.
Let's create a simple example to demonstrate the problem.
Spring Boot 2.6.2 is used in the example
Consider a simple application whose purpose is to call one endpoint several times and record the duration of requests.
Of course, you have also configured the restTemplate available timeouts.
Use one of your favourite rest mocking tools (e.g. Mockoon) to mock the URL that we will call (localhost:8123). Set response to OK and also set the response delay to 5sec.
Now run it — you should get the correct output, which is the timeout of all requests in the set time period.
I/O error on GET request for "http://localhost:8123": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
I/O error on GET request for "http://localhost:8123": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
Request duration in Ms: 2030
Request duration in Ms: 2030
And now comes the tricky part.
Just add apache httpclient as a dependency.
implementation("org.apache.httpcomponents", "httpclient")
And run it again. This time some of the requests exceed the timeout easily.
Request duration in Ms: 2042
I/O error on GET request for "http://localhost:8123": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
Request duration in Ms: 2043
I/O error on GET request for "http://localhost:8123": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
I/O error on GET request for "http://localhost:8123": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
I/O error on GET request for "http://localhost:8123": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
Request duration in Ms: 4045
Request duration in Ms: 4046
How is it possible
Well, it is a part of “spring boot magic”. RestTemplate uses ClientHttpRequestFactory
to create the request. To create this factory, the ClientHttpRequestFactorySupplier
class is used, which scans the classpath when creating the factory, and if httpClient is found among the dependencies, the HttpComponentsClientHttpRequestFactory
is created instead of the SimpleClientHttpRequestFactory
that is used by default. As a consequence, the httpClient, provided by the dependency we are added, is used. This client uses a PoolingHttpClientConnectionManager
and this is why our requests can live longer than the set timeouts.
Let’s say the pool size is 5. We create 6 requests — 5 requests start executing, but the 6th request is waiting to get a connection from the pool. Only when the first of the 5 requests reaches the timeout, the 6th request gets a connection and starts executing. This can lead to large delays when there are a large number of requests. Ther is a 3rd timeout to set “the timeout how long we are willing to wait to get the connection from the pool” The problem is the default value is “infinite” and there is no way to set it via restTemplateBuilder.
Solution
There are multiple solutions to deal with this. One is to create a RequestFactory ourselves and set up what we want. In the RequestConfig
we are able to set all the 3 timeouts.
Hope this was helpful and saved you some time ;)