Timeout Pattern in Microservices
Building resilient microservices: a deep dive into the Timeout pattern. In a microservices system, services constantly call each other over the network. Sooner or later, one of those calls will hang: a service freezes, a database locks up, or a network link silently drops packets. The Timeout pattern makes sure your service never waits forever.
This guide explains why timeouts are the most important resilience setting, the different types of timeouts, and how to configure them in Java and Spring Boot on HTTP clients and with Resilience4j. It also covers what to do after a timeout and how timeouts work together with other resilience patterns.
1. What Is the Timeout Pattern?
The Timeout pattern sets a maximum time that a service will wait for an operation, such as a call to another service or a database query. If the operation does not finish in time, the caller stops waiting, frees its resources and handles the situation, for example by returning a fallback or an error.
When implementing timeout pattern, it’s important to choose an appropriate timeout value for each operation. The timeout value should be set based on the expected response time of the service or operation, and it should be long enough to allow for normal operation but short enough to prevent the system from becoming overloaded.
Configure timeout values based on service-level agreements (SLAs) and downstream service latency. Combine timeouts with circuit breakers and carefully configured retries to improve fault tolerance, prevent resource exhaustion, and build resilient microservices.
It is also important to handle timeouts gracefully by returning meaningful error messages or fallback responses to clients. This allows clients to recognize timeout failures and respond appropriately, such as retrying the request when safe or switching to an alternative service.
2. Why Microservices Need Timeouts
A slow service is often more dangerous than a service that is completely down. A down service fails fast: the connection is refused immediately. A slow or hung service keeps the caller waiting, holding a thread, a connection and memory the whole time.
This is a cascading failure: one slow dependency exhausts the resources of every service that calls it, directly or indirectly. Many HTTP clients and drivers have very long or even infinite default timeouts, so without explicit configuration this can happen in any system.
3. Types of Timeouts
| Timeout | What it limits | Typical value inside a data center |
|---|---|---|
| Connect timeout | Time to open a network connection to the server | 0.5–2 s |
| Read (response) timeout | Time to wait for response data once connected | 1–5 s, based on the service |
| Connection pool timeout | Time to wait for a free connection from a pool | 0.5–3 s |
| Overall (total) timeout | Total time for the whole operation, including retries | Within the user’s time budget |
| Idle timeout | How long an unused connection stays open | Tens of seconds to minutes |
| Query / transaction timeout | Maximum time for a database query or transaction | 1–10 s for user requests |
A connect timeout alone is not enough: a server can accept the connection quickly and then never send a response. Always set both a connect and a read timeout, and for user-facing requests also an overall limit.
4. How a Timeout Works
0.0 s Order Service sends request to Payment Service
... waiting ...
2.0 s No response yet -> timeout fires
Order Service stops waiting, frees the thread
-> returns a fallback / error / tries again (if safe)
(Payment Service may still be working on the request!)
The last line matters: a timeout only stops the caller from waiting. The other service may still finish the work later. Section 8 explains how to handle this safely.
5. A Practical Example: Order Service and a Slow Payment Service
An order service calls a payment service, which normally answers in about 200 ms.
5.1 Without a timeout
- The payment service hits a database lock and stops responding.
- Every order request now waits, possibly for minutes.
- Within seconds, all of the order service’s request threads are waiting.
- The order service stops answering any request, including “view my orders”, which does not need payments at all.
5.2 With a timeout
- The order service waits at most 2 seconds for the payment service.
- After 2 seconds, it stops waiting and tells the user: “We could not confirm your payment yet. Please try again in a moment.”
- Threads are freed quickly, so other features keep working.
- Combined with a circuit breaker, the order service soon stops calling the stuck payment service altogether, until it recovers.
6. Timeouts on HTTP Clients in Spring
6.1 RestClient
Configure timeouts once, for every RestClient.Builder Spring injects, using a customizer. This example uses the JDK’s built-in HTTP client:
package com.example.order.config;
import java.net.http.HttpClient;
import java.time.Duration;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.JdkClientHttpRequestFactory;
@Configuration
public class HttpTimeoutConfig {
@Bean
public RestClientCustomizer httpTimeouts() {
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(1)) // connect timeout
.build();
JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory(httpClient);
factory.setReadTimeout(Duration.ofSeconds(2)); // response timeout
return builder -> builder.requestFactory(factory);
}
}
If one dependency needs different values, for example a slow reporting service, build its client with its own request factory instead of the shared one.
6.2 WebClient
For reactive code, set timeouts on the underlying Reactor Netty client:
package com.example.order.config;
import java.time.Duration;
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;
import io.netty.channel.ChannelOption;
import reactor.netty.http.client.HttpClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClient inventoryWebClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1_000) // connect timeout
.responseTimeout(Duration.ofSeconds(2)); // response timeout
return builder
.baseUrl("http://inventory-service")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
You can also limit a single call with the Reactor timeout operator, for example .bodyToMono(Stock.class).timeout(Duration.ofSeconds(2)).
6.3 OpenFeign
spring:
cloud:
openfeign:
client:
config:
default: # all Feign clients
connect-timeout: 1000 # milliseconds
read-timeout: 2000
payment-service: # override for one client
read-timeout: 4000
7. Timeouts with Resilience4j TimeLimiter
Resilience4j’s TimeLimiter adds timeouts declaratively and works well with its retry and circuit breaker modules:
resilience4j:
timelimiter:
instances:
shipping:
timeout-duration: 2s
cancel-running-future: true # try to cancel the work when time is up
package com.example.order.shipping;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import org.springframework.stereotype.Service;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
@Service
public class ShippingQuoteService {
private final ShippingProviderClient provider;
public ShippingQuoteService(ShippingProviderClient provider) {
this.provider = provider;
}
@TimeLimiter(name = "shipping", fallbackMethod = "standardQuote")
public CompletableFuture<ShippingQuote> quote(Order order) {
return CompletableFuture.supplyAsync(() -> provider.getQuote(order));
}
// Used when the provider does not answer within 2 seconds
private CompletableFuture<ShippingQuote> standardQuote(Order order, TimeoutException ex) {
return CompletableFuture.completedFuture(ShippingQuote.standardRate(order));
}
}
The @TimeLimiter annotation works on methods returning CompletableFuture (or reactive types). It requires the resilience4j-spring-boot3 dependency and Spring AOP.
8. What to Do After a Timeout
A timeout tells you that you did not get an answer in time, not that the operation failed. The other service may still complete it. Decide in advance how to react:
| Option | When to use it | Watch out for |
|---|---|---|
| Return a fallback | Optional data: recommendations, reviews, estimates | Make it clear to users when data is incomplete |
| Retry | Idempotent operations, such as reading data | Only within the remaining time budget; use backoff |
| Report “pending” | Important actions that may have succeeded, such as payments | Check the real status later, or let the other service notify you |
| Return an error | When nothing useful can be shown | Use 504 Gateway Timeout or 503 with a clear message |
For actions that change data, always send an idempotency key, such as the order ID. Then a retry after a timeout can never charge a customer twice, even if the first request actually succeeded. See the Retry pattern.
9. Combining Timeouts with Other Resilience Patterns
Timeouts work best when combined with other resilience patterns in microservices. While a timeout limits how long a service waits for a response, other patterns help manage failures, prevent resource exhaustion, and improve system reliability.
| Pattern | How it works with timeouts |
|---|---|
| Retry | A timed-out call can be retried, if safe; each attempt has its own timeout, and all attempts fit in the total budget |
| Circuit breaker | Counts timeouts and slow calls as failures; when too many occur, it stops calling the service for a while |
| Bulkhead | Limits how many calls can wait at the same time, so even short timeouts cannot use up every thread |
| Fallback | Gives users a useful answer when the timeout fires |
Timeouts are the foundation: without them, retries never get a chance to run, and circuit breakers only notice failures far too late.
10. Best Practices
- Set a timeout on every remote call: HTTP, gRPC, database, cache and message broker clients.
- Never rely on defaults; many are very long or infinite.
- Set both connect and read timeouts, plus an overall limit for user requests.
- Base values on measured percentiles, not guesses.
- Make inner timeouts shorter than outer ones, and keep everything within the user’s time budget.
- Configure timeouts centrally, with per-dependency overrides where needed.
- Plan the reaction: fallback, safe retry, “pending” status or a clear error.
- Use idempotency keys for operations that change data.
- Combine with circuit breakers and bulkheads.
- Monitor timeouts and response times, and review values regularly.
11. Common Mistakes to Avoid
| Mistake | What goes wrong | Better approach |
|---|---|---|
| No timeout at all | One hung service blocks threads until the caller fails too | Set explicit timeouts everywhere |
| Only a connect timeout | A server that accepts but never answers still blocks the caller | Also set a read timeout |
| Timeouts longer than the caller’s | Inner work continues after the user has already given up | Make inner timeouts shorter |
| The same value for everything | Fast services wait too long; slow ones fail too often | Set values per dependency |
| Retrying with full timeouts each time | Total time far exceeds what users accept | Fit all attempts into one budget |
| Assuming a timeout means “not done” | Duplicate payments or orders after a retry | Use idempotency keys and check status |
orTimeout without client timeouts | Background work keeps running and piles up | Also set timeouts on the underlying client |
12. Summary
The Timeout pattern is the simplest and most important resilience technique in microservices. By setting a maximum wait for every remote operation, it stops slow or hung dependencies from tying up threads and spreading failures across the system. Configure connect, read and overall timeouts on your HTTP clients and with Resilience4j, and decide in advance what happens when time runs out. Combined with retries, circuit breakers, bulkheads and fallbacks, timeouts keep your services fast, responsive and resilient, even when parts of the system are struggling. Continue with the Circuit Breaker pattern to learn how to stop calling a service that keeps timing out.
Timeout Pattern: Interview Questions
Common interview questions on the Timeout pattern, from the basics to design scenarios, with short answers you can explain in your own words.
Basic questions
Q. What is the Timeout pattern, and why do microservices need it?
Answer: The Timeout pattern sets a maximum time to wait for a remote operation, such as a call to another service or a database. If no answer arrives in time, the caller stops waiting and handles the failure, for example with a fallback or an error.
Microservices make many network calls, and any of them can hang. Without timeouts, one slow dependency can block threads in every service that calls it.
Q. Why is a slow dependency more dangerous than one that is completely down?
Answer: A service that is down usually fails fast: the connection is refused and the caller gets an error in milliseconds.
A slow service keeps callers waiting while they hold threads and connections. As requests pile up, the caller runs out of threads and becomes slow too, so the problem spreads up the call chain. This is called a cascading failure, and timeouts are the first defence against it.
Q. What is the difference between a connect timeout and a read (response) timeout?
Answer: A connect timeout limits how long the client waits to open a TCP connection to the other service. It should be short, often around one second, because connecting is normally fast.
A read timeout (called a response timeout in some clients) limits how long the client waits for data after the request has been sent. It depends on how long the operation normally takes, so it is usually longer than the connect timeout.
Q. What happens if you do not configure any timeout on an HTTP client?
Answer: Many Java clients then wait indefinitely. HttpURLConnection uses no timeout by default, and the JDK HttpClient has no request timeout unless you set one.
A single hung dependency can then block request threads forever. That is why every remote call should have an explicit connect and read timeout.
Intermediate questions
Q. How do you configure timeouts for RestClient, WebClient and OpenFeign in Spring Boot?
Answer:
- RestClient: give it a request factory with timeouts, for example a
JdkClientHttpRequestFactorywith a connect timeout on theHttpClientandsetReadTimeouton the factory, applied to every builder through aRestClientCustomizer. - WebClient: configure the Reactor Netty
HttpClientwithChannelOption.CONNECT_TIMEOUT_MILLISandresponseTimeout, or limit one call with.timeout(Duration). - OpenFeign: set
connect-timeoutandread-timeoutunderspring.cloud.openfeign.client.config, as defaults or per client.
Q. What does Resilience4j TimeLimiter do, and how is it different from an HTTP client timeout?
Answer: TimeLimiter limits how long an asynchronous operation, such as a CompletableFuture, may take. When time runs out, it completes with a TimeoutException and can call a fallback method.
An HTTP client timeout works at the network level and closes or abandons the connection. A TimeLimiter only stops waiting: the underlying work may keep running unless it can be cancelled (cancel-running-future). In practice you use both: client timeouts on every call, and TimeLimiter for an overall limit and a fallback.
Q. When a call times out, does the downstream service stop processing the request?
Answer: No. A timeout only stops the caller from waiting. The downstream service may still finish the work, for example charge a card or create a shipment, after the caller has given up.
So a timeout means "result unknown", not "it did not happen". Make such operations idempotent (for example with idempotency keys) and check the status before acting again.
Q. How do you choose a good timeout value?
Answer: Base it on measured response times, not guesses. A common starting point is the 99th percentile latency of the dependency when it is healthy, plus a small margin.
The value must also fit inside what the caller can afford: the whole user request has a limit, so each call gets only part of it. Review the values regularly as traffic and performance change.
Q. Should you retry after a timeout?
Answer: Only when it is safe. Reads and other idempotent operations can be retried; operations with side effects, such as payments, need an idempotency key so the downstream service can detect duplicates.
Limit the number of attempts, wait with backoff and jitter between them, and make sure all attempts together still fit within the caller's overall time limit. Otherwise retries multiply the waiting time instead of hiding the failure.
Q. How do timeouts and circuit breakers work together?
Answer: A timeout limits a single call; a circuit breaker watches many calls. Timed-out calls are recorded as failures (Resilience4j can also count slow calls with slowCallDurationThreshold).
When too many calls fail or are slow, the breaker opens and later calls fail immediately instead of waiting for the timeout again. This protects both the caller and the struggling service while it recovers.
Q. What HTTP status code should a service return when a downstream call times out?
Answer: Gateways and proxies typically return 504 Gateway Timeout. A service that cannot complete a request because a dependency is too slow often returns 503 Service Unavailable or 504, or a successful response with fallback data if a degraded answer is acceptable.
Do not use 408 Request Timeout: it means the client was too slow to send its request, not that a downstream service was slow.
Tricky and scenario questions
Q. What is a timeout budget, and why should inner timeouts be shorter than outer ones?
Answer: A timeout budget is the total time a request may take from end to end, for example 3 seconds for a checkout. Each hop and each retry spends part of that budget.
If an inner call can wait longer than its caller, the caller gives up first while the inner work keeps running, which wastes resources and can produce duplicate work. Giving each layer a slightly shorter timeout than the one above it keeps the chain consistent.
Q. What is deadline propagation?
Answer: Instead of passing a fixed timeout to each service, the caller passes the remaining time (or an absolute deadline) along with the request. Each service uses what is left for its own calls and stops early if the deadline has already passed.
gRPC supports deadlines natively; with HTTP, teams usually pass the deadline in a custom header. It prevents services from working on requests whose callers have already given up.
Q. How would you test timeout behavior?
Answer: Simulate a slow dependency and check what your service does. Tools such as WireMock (with a fixed or random response delay) or Toxiproxy (which adds latency to a real connection) make this easy.
Assert that the call fails within the expected time, that the fallback or error response is correct, and that no threads stay blocked. Run the same experiments under load to see how the whole system behaves.
Q. During a sale, all Order Service threads are stuck waiting on a slow Payment Service and the whole shop becomes slow. How would you fix it?
Answer: First, add connect and read timeouts to the payment client so threads are released quickly. Next, wrap the call in a circuit breaker so that, while Payment Service is struggling, orders fail fast instead of waiting.
Then decide what the user sees: for example keep the order as "payment pending" and confirm it later, since a timed-out payment may still have succeeded. Use idempotency keys for any retry, and consider a bulkhead so slow payment calls cannot use up all threads.