A downstream timeout is one part of the total deadline for a request that may also wait for a connection, execute SQL and serialize a response.
Spring HTTP client timeouts: bound the call inside the request deadline
Spend one budget
Suppose the public receipt endpoint allows 900ms. Spending 700ms on a risk lookup leaves only 200ms for connection acquisition, local validation, transaction commit and response delivery. Two retries of that 700ms call cannot fit. Write down a maximum elapsed time for the whole request, then allocate connect, response and retry budgets beneath it.
Different request factories expose different timeout controls. Select the actual factory first; test connect refusal, a server that accepts but never responds, and a slow body. A successful localhost response says nothing about these paths. The transport choice is part of the contract.
Keep transactions short
Do not hold a database transaction open while waiting on a slow third-party request unless the business rule truly requires it and the lock cost is understood. A client timeout after the remote server commits can still leave an uncertain result. The outbox boundary records local intent atomically, then performs external delivery separately.
The kit now checks an 80ms read timeout against a loopback server that accepts the request and stalls. The snippet remains a budget worksheet; it does not establish a 900ms end-to-end service deadline.
Working sketch
public request deadline: 900ms
connection acquisition + connect: 120ms
upstream response: 450ms
local transaction and response reserve: 330msVerification boundary
RestClientBoundaryTest.readTimeoutStopsWaitingForAStalledResponse checks a local HTTP read timeout. OutboxRelayFlowTest covers a separate fake-transport retry boundary.
Costs and limits
The budget is illustrative. Production values require measured latency distributions, the chosen transport and real upstream failure tests.
Common Mistakes
- Do not add per-attempt timeouts that exceed the public request deadline.
- Do not keep a database transaction open across an avoidable network wait.
- Do not infer remote non-commit from a client-side timeout.
Read next
Spring RestClient request factories: pin the transport you test, Spring HTTP retries: only replay a command with a defined identity, Spring transactional outbox: commit a receipt and event row together, Spring transaction propagation: joined rollback and independent commit.
Checked outbound HTTP continuation
Continue with Spring RestClient read timeout: reject a server that accepts but stalls.
