00:00

Timeout Pattern in Microservices

Last updated:

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.

How one slow service spreadsPayment Service hangs, so its calls hang instead of failing fast. Order Service threads wait for Payment, each call holding a thread, until all 200 threads are busy. The API Gateway then waits for Order Service, requests queue up and the gateway slows down. Finally users see the whole shop as down, even pages that never use payments.Payment Service hangsno answer and no errorCalls hang instead of failing fastspreads to the callerOrder Service threads wait for Paymenteach call holds a threadAll 200 threads busyspreads to the callerAPI Gateway waits for Order Servicerequests queue up at the gatewayGateway slows downspreads to the callerUsers see the whole shop as "down"every page is slow or failsEven pages that never use payments
A cascading failure: one hung dependency ties up threads in every service above it, until the whole shop looks down.

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

TimeoutWhat it limitsTypical value inside a data center
Connect timeoutTime to open a network connection to the server0.5–2 s
Read (response) timeoutTime to wait for response data once connected1–5 s, based on the service
Connection pool timeoutTime to wait for a free connection from a pool0.5–3 s
Overall (total) timeoutTotal time for the whole operation, including retriesWithin the user’s time budget
Idle timeoutHow long an unused connection stays openTens of seconds to minutes
Query / transaction timeoutMaximum time for a database query or transaction1–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

Timeline of a call with a 2-second timeout
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.

Order Service calling a slow Payment Service, without and with a timeoutWithout a timeout, Order Service waits for minutes on Payment Service, which is stuck on a database lock. All of its threads end up waiting, and Order Service stops answering every request, even view my orders. With a 2 second timeout, Order Service gives up after 2 seconds, its threads are freed quickly, the user sees please try again, and other features keep working.Without a timeoutOrder ServicePayment Servicedatabase lockwaits minutesAll threads waitingOrder Service stops answeringevery request, even "view my orders"With a 2 s timeoutOrder ServicePayment Servicedatabase lockstops at 2 sThreads freed quicklyUser sees "Please try again"other features keep working
The same stuck Payment Service: without a timeout it takes Order Service down; with a 2-second timeout only the payment step fails.

5.1 Without a timeout

  1. The payment service hits a database lock and stops responding.
  2. Every order request now waits, possibly for minutes.
  3. Within seconds, all of the order service’s request threads are waiting.
  4. The order service stops answering any request, including “view my orders”, which does not need payments at all.

5.2 With a timeout

  1. The order service waits at most 2 seconds for the payment service.
  2. After 2 seconds, it stops waiting and tells the user: “We could not confirm your payment yet. Please try again in a moment.”
  3. Threads are freed quickly, so other features keep working.
  4. 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:

HttpTimeoutConfig.java
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:

WebClientConfig.java
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

application.yml
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:

application.yml
resilience4j:
  timelimiter:
    instances:
      shipping:
        timeout-duration: 2s
        cancel-running-future: true   # try to cancel the work when time is up
ShippingQuoteService.java
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:

OptionWhen to use itWatch out for
Return a fallbackOptional data: recommendations, reviews, estimatesMake it clear to users when data is incomplete
RetryIdempotent operations, such as reading dataOnly within the remaining time budget; use backoff
Report “pending”Important actions that may have succeeded, such as paymentsCheck the real status later, or let the other service notify you
Return an errorWhen nothing useful can be shownUse 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.

PatternHow it works with timeouts
RetryA timed-out call can be retried, if safe; each attempt has its own timeout, and all attempts fit in the total budget
Circuit breakerCounts timeouts and slow calls as failures; when too many occur, it stops calling the service for a while
BulkheadLimits how many calls can wait at the same time, so even short timeouts cannot use up every thread
FallbackGives 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

  1. Set a timeout on every remote call: HTTP, gRPC, database, cache and message broker clients.
  2. Never rely on defaults; many are very long or infinite.
  3. Set both connect and read timeouts, plus an overall limit for user requests.
  4. Base values on measured percentiles, not guesses.
  5. Make inner timeouts shorter than outer ones, and keep everything within the user’s time budget.
  6. Configure timeouts centrally, with per-dependency overrides where needed.
  7. Plan the reaction: fallback, safe retry, “pending” status or a clear error.
  8. Use idempotency keys for operations that change data.
  9. Combine with circuit breakers and bulkheads.
  10. Monitor timeouts and response times, and review values regularly.

11. Common Mistakes to Avoid

MistakeWhat goes wrongBetter approach
No timeout at allOne hung service blocks threads until the caller fails tooSet explicit timeouts everywhere
Only a connect timeoutA server that accepts but never answers still blocks the callerAlso set a read timeout
Timeouts longer than the caller’sInner work continues after the user has already given upMake inner timeouts shorter
The same value for everythingFast services wait too long; slow ones fail too oftenSet values per dependency
Retrying with full timeouts each timeTotal time far exceeds what users acceptFit all attempts into one budget
Assuming a timeout means “not done”Duplicate payments or orders after a retryUse idempotency keys and check status
orTimeout without client timeoutsBackground work keeps running and piles upAlso 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.