Spring REST API Request Timeout: The Complete Guide
Learn how to set and manage request timeouts in Spring REST APIs.
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
- ✓Basic knowledge of Spring Boot and REST APIs
- ✓Familiarity with HTTP clients (RestTemplate or WebClient)
- ✓Understanding of asynchronous processing in Spring MVC (optional)
- Use
spring.mvc.async.request-timeoutfor async requests (Servlet 3.0+). - Use
WebClientwithresponseTimeout()for reactive stacks. - Use
RestTemplatewithRequestConfig.setConnectTimeout()for blocking calls. - Always set connect, read, and write timeouts separately.
- Never rely on default timeouts โ they are infinite.
Imagine you order a pizza and the restaurant says 'we'll deliver when it's ready' but never gives a time. You'd wait forever. A request timeout is like telling the restaurant 'if you can't deliver within 30 minutes, cancel my order.' In APIs, it prevents your app from hanging indefinitely when a downstream service is slow or down.
You've built a beautiful Spring REST API. It's deployed, users are happy, and then one day โ users start seeing spinning wheels, timeouts, and 503s. Your logs show threads piling up, memory climbing, and eventually the app falls over. The culprit? A downstream service that took 30 seconds to respond, and your API had no timeout configured.
I've seen this exact scenario kill production apps more times than I can count. A fintech client of mine had their payment processing grind to a halt because a third-party fraud check service went down. Their RestTemplate calls had no timeout, so every request blocked a thread for 45 seconds until TCP gave up. Within minutes, all 200 Tomcat threads were stuck, and the app became unresponsive.
The hard truth: most Spring developers don't think about timeouts until it's too late. Default timeouts in Java HTTP clients are often infinite (or absurdly long). Spring Boot doesn't enforce timeouts out-of-the-box. You must be explicit.
In this guide, I'll show you exactly how to configure timeouts for every HTTP client Spring offers: RestTemplate, WebClient, and async request handling. I'll share production debugging techniques, common mistakes, and the one approach I always use in critical-path APIs. By the end, you'll never ship a Spring API without proper timeouts again.
Understanding HTTP Timeout Types
Before diving into Spring configurations, you need to understand the three distinct timeout types that exist at the HTTP client level. Mixing them up is a common source of bugs.
Connection Timeout โ The maximum time to establish a TCP connection. If the server is unreachable, this timeout fires. Set it low (1-3 seconds) because if you can't connect quickly, retrying is usually pointless.
Read Timeout (Socket Timeout) โ The maximum time to wait for data after the connection is established. This is the most important timeout for slow services. A server that accepts the connection but never sends a response will hang here. Set based on your SLA โ I use 5 seconds for most internal services, 10 seconds for external.
Write Timeout โ The maximum time to send the request body. Rarely an issue unless you're uploading large files. Set it to match your expected upload time plus a buffer.
Spring's RestTemplate and WebClient handle these differently. RestTemplate uses Apache HttpClient's RequestConfig (connect and socket timeout). WebClient uses responseTimeout (which covers both connect and read).
Production Insight: In a past project, a developer set only the connection timeout but not the read timeout. The service responded quickly initially, but after a deployment, it started processing requests in a batch and responses were delayed. The connection succeeded, but the read hung for 2 minutes. Always set both.
Configuring Timeouts for RestTemplate
RestTemplate is the classic blocking HTTP client. It's simple, but its default behavior can kill your app. Here's how to configure it properly.
Step 1: Replace the default request factory. Spring Boot auto-configures RestTemplate with SimpleClientHttpRequestFactory (JDK's HttpURLConnection) which has no timeout methods. You need to use HttpComponentsClientHttpRequestFactory (Apache HttpClient) or Netty4ClientHttpRequestFactory.
Step 2: Build a custom RestTemplate bean. I always create a dedicated configuration class for HTTP clients. This keeps timeout settings in one place and makes them testable.
Step 3: Use application properties for external configuration. Hardcoding timeouts is a maintenance nightmare. Use @Value or @ConfigurationProperties to externalize them.
Step 4: Don't forget the connection pool. Timeouts alone won't save you if the pool is too small. Configure max connections and per-route limits.
Here's a production-ready RestTemplate configuration:
```java @Configuration public class RestTemplateConfig {
@Value("${http.client.connect-timeout:2000}") private int connectTimeout;
@Value("${http.client.read-timeout:5000}") private int readTimeout;
@Value("${http.client.conn-request-timeout:2000}") private int connRequestTimeout;
@Value("${http.client.max-connections:200}") private int maxConnections;
@Bean public RestTemplate restTemplate() { RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(connectTimeout) .setSocketTimeout(readTimeout) .setConnectionRequestTimeout(connRequestTimeout) .build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(maxConnections); connectionManager.setDefaultMaxPerRoute(maxConnections);
CloseableHttpClient httpClient = HttpClients.custom() .setDefaultRequestConfig(requestConfig) .setConnectionManager(connectionManager) .build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory); } } ```
Production Insight: I once saw a team set a 30-second read timeout on a critical payment endpoint. The downstream service had a 99th percentile of 2 seconds. The 30-second timeout masked a performance regression for weeks. Always base your timeouts on actual SLAs, not arbitrary numbers.
Configuring Timeouts for WebClient (Reactive Stack)
WebClient is the modern, non-blocking HTTP client introduced in Spring 5. It's the recommended client for new projects, especially if you're using WebFlux. But timeouts work differently here.
The key method: responseTimeout() โ This sets the maximum time to wait for a response, including connection and read. It's a single Duration parameter.
Channel options โ For fine-grained control, you can set connect timeout and read/write idle timeouts on the underlying Netty connection.
Per-request vs global โ You can set a default timeout on the WebClient builder, and override it per request using httpRequest().
Here's how to configure WebClient with timeouts:
```java @Configuration public class WebClientConfig {
@Bean public WebClient webClient() { return WebClient.builder() .baseUrl("https://api.example.com") .clientConnector(new ReactorClientHttpConnector( HttpClient.create() .responseTimeout(Duration.ofSeconds(5)) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000) .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5)) .addHandlerLast(new WriteTimeoutHandler(5)) ) )) .build(); } } ```
Per-request timeout override: ``java webClient.get() .uri("/slow-endpoint") .httpRequest(httpRequest -> { HttpClientRequest reactorRequest = httpRequest.getNativeRequest(); reactorRequest.responseTimeout(Duration.ofSeconds(10)); }) .retrieve() .bodyToMono(String.class); ``
Production Insight: WebClient's responseTimeout is a 'response timeout', not a 'read timeout'. It starts when the request is sent and includes the entire response time. If you need separate connect and read timeouts, use Netty channel options as shown above. I've seen cases where a slow TLS handshake caused timeouts even though the server was fast โ the connect timeout was too low.
Async Request Timeout in Spring MVC
Spring MVC supports asynchronous request processing via DeferredResult, Callable, or WebAsyncTask. This allows you to free up the container thread while waiting for a long computation or downstream call. But you must set a timeout, or the request can hang indefinitely.
The property: spring.mvc.async.request-timeout โ This sets the timeout for async requests in milliseconds. Default is infinite (or 10 seconds in some versions). Set it to a value that matches your maximum acceptable response time.
Example with DeferredResult:
``java @GetMapping("/async") public DeferredResult<String> asyncEndpoint() { DeferredResult<String> deferredResult = new DeferredResult<>(5000L); // 5s timeout CompletableFuture.supplyAsync(() -> { // Simulate long task try { Thread.sleep(3000); } catch (InterruptedException e) { } return "Done"; }).thenAccept(deferredResult::setResult); deferredResult.onTimeout(() -> deferredResult.setErrorResult( ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body("Timeout") ) ); return deferredResult; } ``
Global configuration in application.yml: ``yaml spring: mvc: async: request-timeout: 10000 ``
Production Insight: I worked with a team that used async requests to call a slow reporting service. They set a 30-second timeout globally, but one report took 45 seconds. The result was a 500 error and a frustrated user. Always set per-request timeouts on DeferredResult if different endpoints have different SLAs.
What the Official Docs Won't Tell You
The Spring documentation covers the basics, but here are the hard-learned lessons from production:
1. Timeouts are not a silver bullet. They prevent thread exhaustion, but they don't handle retries or fallbacks. Combine timeouts with circuit breakers (Resilience4j) and retries (with exponential backoff) for real resilience.
2. DNS resolution can be slow. The connection timeout doesn't include DNS lookup. If your DNS server is slow, you might see longer delays than expected. Use a DNS caching library or set JVM DNS TTL.
3. Load balancers and proxies add latency. If your API sits behind a load balancer (like AWS ALB), the timeout you set on the client might not match the timeout on the load balancer. A common issue: client timeout is 10s, but ALB timeout is 5s, so ALB returns 504 before your client times out. Align timeouts across the entire chain.
4. Spring Cloud Gateway has its own timeout configuration. If you use Spring Cloud Gateway, you need to set timeouts on the routes, not just on the downstream clients. Use spring.cloud.gateway.routes[0].metadata.response-timeout.
5. Testing timeouts is hard. Unit tests with mocked HTTP clients won't exercise real timeouts. Use integration tests with WireMock to simulate slow responses. I've caught many timeout misconfigurations this way.
6. Thread pool sizing matters. If you have 200 Tomcat threads and each downstream call takes 5 seconds, you can handle at most 40 requests per second. Timeouts help, but you also need to monitor thread pool utilization and scale accordingly.
7. Beware of infinite timeouts in default configurations. I've seen Spring Boot 2.x apps with no explicit timeouts run fine for months until a downstream service slowed down. Then the app died in minutes. Always set timeouts, even if you think they're unnecessary.
Global Timeout Configuration with Spring AOP
If you have many HTTP clients in your application, configuring each one individually is error-prone. You can use Spring AOP to enforce a global timeout on all RestTemplate or WebClient calls. This is a pattern I've used in large microservices to ensure no call escapes without a timeout.
The idea: Create an aspect that wraps the execution of RestTemplate's exchange methods and sets a timeout using RequestConfig or HttpRequest. However, this is tricky because RestTemplate's client is usually already configured.
A simpler approach: Use a custom ClientHttpRequestInterceptor that sets timeouts on the underlying request. Here's an example for RestTemplate:
```java @Component public class TimeoutInterceptor implements ClientHttpRequestInterceptor {
@Value("${http.client.read-timeout:5000}") private int readTimeout;
@Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // For HttpComponents, we can't easily modify timeout after creation. // Instead, use a wrapper factory. return execution.execute(request, body); } } ```
But honestly, AOP for timeouts is overengineering. A better pattern is to create a single RestTemplate bean with proper timeouts and inject it everywhere. If you need different timeouts for different services, create multiple beans with @Qualifier.
Production Insight: In a 50-microservice deployment, we used a shared library that provided a pre-configured RestTemplate with timeouts. This ensured consistency. We also added metrics to track timeout exceptions per service. This helped us identify which downstream services were slow and needed attention.
The 2AM Outage: When No Timeout Brought Down a Payment Gateway
RequestConfig with no timeout. A downstream fraud detection service had a memory leak, causing responses after 45 seconds. All Tomcat threads were blocked waiting, and new requests queued until the thread pool exhausted.- Always set explicit timeouts on HTTP clients โ never rely on defaults.
- Timeouts should be tuned per endpoint based on SLA requirements.
- Combine timeouts with circuit breakers for resilience.
- Monitor thread pool usage and timeout exception rates in production.
- Test timeout behavior under load with chaos engineering.
spring.mvc.async.request-timeout or check if the deferred result is being completed on time.new RequestConfig.Builder().setConnectTimeout(2000).setSocketTimeout(5000).build()restTemplate.setRequestConfig(config)| File | Command / Code | Purpose |
|---|---|---|
| HttpClientConfig.java | @Configuration | Understanding HTTP Timeout Types |
| application.yml | http: | Configuring Timeouts for RestTemplate |
| WebClientTimeoutTest.java | public class WebClientTimeoutTest { | Configuring Timeouts for WebClient (Reactive Stack) |
| AsyncTimeoutConfig.java | @Configuration | Async Request Timeout in Spring MVC |
| WireMockTest.java | @SpringBootTest | What the Official Docs Won't Tell You |
| SharedRestTemplateConfig.java | @Configuration | Global Timeout Configuration with Spring AOP |
Key takeaways
Interview Questions on This Topic
Explain the three types of HTTP timeouts and how you would configure them in a Spring RestTemplate.
RequestConfig on an Apache HttpClient, then set it on HttpComponentsClientHttpRequestFactory. Example: RequestConfig.custom().setConnectTimeout(2000).setSocketTimeout(5000).setConnectionRequestTimeout(2000).build().Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
That's Spring Boot. Mark it forged?
6 min read · try the examples if you haven't