External API Client Rules
1. Timeout Configuration Principles
Recommended Defaults
| Setting |
Default |
Description |
| Connect timeout |
3s |
TCP connection setup |
| Read timeout |
10s |
Waiting for response body |
| Write timeout |
10s |
Sending request body |
| Connection pool |
20 |
Max concurrent connections |
| Idle timeout |
60s |
Idle connection lifetime |
Timeout Types Explained
| Timeout |
What It Measures |
Too Low |
Too High |
| Connect timeout |
TCP handshake + TLS negotiation |
Fails on slow networks or cold starts |
Threads blocked on unreachable hosts |
| Read timeout |
Time to receive first/full response body |
Fails on legitimate slow responses |
Threads held waiting for hung services |
| Write timeout |
Time to send request body |
Fails on large uploads over slow links |
Usually less critical than read timeout |
Timeout Rules
- Always set explicit timeouts — never rely on defaults (which may be infinite)
- Set read timeout based on the slowest expected response from upstream
- Set connect timeout shorter than read timeout (connection should be fast; 1-5s typical)
- Write timeout matters primarily for large request bodies (file uploads, bulk data)
- Use shorter timeouts for non-critical calls
- Log timeout values at startup for debugging
- For downstream SLA compliance: set total timeout (connect + read) below your own SLA budget minus processing time
2. Error Handling Strategy
Error Classification
| Exception Source |
Cause |
Action |
| HTTP response error |
4xx/5xx response |
Map to domain error |
| Network/timeout error |
Connection failure |
Retry or circuit break |
| Parsing/decoding error |
Malformed response |
Log and fail |
Error Handling Rules
- Never let raw HTTP client exceptions propagate to callers
- Wrap in domain-specific exceptions (e.g.,
PaymentApiException)
- Log response status and body on errors (but mask sensitive data)
- Distinguish between retryable (network, 503) and non-retryable (400, 404) errors
3. Retry Strategy
Retry Decision Table
| Scenario |
Retry |
Max Attempts |
Backoff |
| Network timeout |
Yes |
3 |
Exponential 2x |
| Connection refused |
Yes |
3 |
Exponential 2x |
| HTTP 429 |
Yes |
3 |
Respect Retry-After |
| HTTP 503 |
Yes |
3 |
Exponential 2x |
| HTTP 4xx (not 429) |
No |
- |
- |
| Parsing error |
No |
- |
- |
Exponential Backoff with Jitter
Exponential backoff prevents overwhelming a recovering service. Jitter prevents synchronized retry storms (thundering herd).
Base formula:
delay = min(base * 2^attempt, max_delay)
Full jitter (recommended — distributes retries evenly):
delay = random(0, min(base * 2^attempt, max_delay))
Equal jitter (balanced between spread and minimum wait):
temp = min(base * 2^attempt, max_delay)
delay = temp/2 + random(0, temp/2)
Example with base=1s, max=30s:
Attempt 1: random(0, 2s)
Attempt 2: random(0, 4s)
Attempt 3: random(0, 8s)
Reference: AWS Architecture Blog — Exponential Backoff and Jitter
Retry Principles
- Always use exponential backoff with jitter to avoid thundering herd
- Prefer full jitter over equal jitter or decorrelated jitter for most use cases
- Set a maximum retry count — never retry indefinitely
- Set a retry budget (e.g., max 20% of requests can be retries) to prevent retry amplification across service layers
- Do not retry inside a database transaction — retry at the outermost layer
- Do not retry non-idempotent requests without an idempotency key
- Ensure retried requests are safe: check that the upstream API is idempotent or use idempotency keys
4. Circuit Breaker Pattern
When to Use
- External APIs with unpredictable latency or availability
- Non-critical dependencies where degraded operation is acceptable
- High-throughput paths where cascading failures are a risk
Key Parameters
| Parameter |
Description |
Typical Value |
| Sliding window size |
Number of calls to evaluate |
10 |
| Failure rate threshold |
Percentage to open circuit |
50% |
| Wait duration in open state |
Cooldown before half-open |
30s |
| Half-open permitted calls |
Test calls before closing |
3 |
| Slow call duration threshold |
What counts as "slow" |
5s |
| Slow call rate threshold |
Percentage of slow calls to open circuit |
80% |
5. Response Mapping Principles
DTO Separation
- Never expose external API DTOs to internal service layers
- Map external responses to domain models at the client boundary
- Handle missing/null fields defensively in external DTOs
Mapping Example
External API Response (their naming) Domain Model (our naming)
───────────────────────────────────── ──────────────────────────
user_id: String → id: String
full_name: String → name: String
email_address: String → email: String
6. Connection Pooling
Why Connection Pooling Matters
Creating a new TCP connection (and TLS handshake) for every request adds 50-200ms of latency. Connection pools maintain reusable connections to amortize this cost.
Pool Configuration Parameters
| Parameter |
Description |
Recommendation |
| Max connections per host |
Max concurrent connections to a single destination |
20-50 (match downstream capacity) |
| Max total connections |
Total connections across all hosts |
100-200 for multi-host clients |
| Idle timeout |
How long an idle connection is kept alive |
60-90s (match server/LB keep-alive) |
| Max connection lifetime |
Max age of a connection regardless of activity |
5-10 minutes |
| Connection acquisition timeout |
Max wait time when pool is exhausted |
5s (fail fast) |
Connection Pool Pitfalls
| Pitfall |
Consequence |
Fix |
| Pool too small for traffic |
Requests queue up waiting for connections |
Size pool based on peak concurrent requests per host |
| Pool too large |
Wastes server resources, may exceed upstream connection limits |
Right-size based on actual concurrency needs |
| No idle timeout |
Stale connections cause errors |
Set idle timeout shorter than server keep-alive |
| No max lifetime |
DNS changes not picked up |
Set max lifetime to force periodic reconnection |
| One pool for all APIs |
Slow API exhausts pool, blocking fast APIs |
Use separate pools (or separate clients) per downstream API |
| Ignoring keep-alive |
Server closes connection but client reuses it |
Respect Connection: close header, match keep-alive settings |
Sizing Guidelines
Pool size per host = peak_concurrent_requests_to_that_host * 1.5
Example:
- Your service handles 1000 req/s
- 30% of requests call Payment API (300 req/s)
- Payment API avg response time: 200ms
- Concurrent connections needed: 300 * 0.2 = 60
- Pool size: 60 * 1.5 = 90
7. Anti-Patterns
- No timeout configuration (risk of thread exhaustion)
- Retrying non-idempotent requests (POST without idempotency key)
- Logging full response bodies in production (performance + data leak)
- Sharing a single HTTP client instance for unrelated APIs with different latency profiles
- Calling external APIs inside database transactions
- Ignoring rate limit headers from upstream APIs
- No circuit breaker on critical external dependencies
- Not setting connection pool idle/max lifetime (stale connections, DNS caching issues)
- Retry amplification across service layers (service A retries 3x, calls service B which retries 3x = 9 attempts)
8. Related Skills
error-handling: HTTP error response handling strategies
observability: HTTP client metrics collection and monitoring
security: API authentication, TLS, header security
caching: HTTP response caching strategies
Additional References
- For circuit breaker, retry, timeout, and bulkhead resilience patterns, see references/resilience-patterns.md
- For Spring Boot implementation patterns (RestClient, Spring Retry, Resilience4j), see
spring-framework skill — references/http-client.md
1---2name: http-client3description: Framework-agnostic external API integration patterns including timeouts, error handling, retry strategy, circuit breaker, and response mapping. Use when writing or reviewing HTTP client code.4license: MIT5---6# External API Client Rules78## 1. Timeout Configuration Principles910### Recommended Defaults1112| Setting | Default | Description |13| ---------------- | ------- | -------------------------- |14| Connect timeout | 3s | TCP connection setup |15| Read timeout | 10s | Waiting for response body |16| Write timeout | 10s | Sending request body |17| Connection pool | 20 | Max concurrent connections |18| Idle timeout | 60s | Idle connection lifetime |1920### Timeout Types Explained2122| Timeout | What It Measures | Too Low | Too High |23| --- | --- | --- | --- |24| Connect timeout | TCP handshake + TLS negotiation | Fails on slow networks or cold starts | Threads blocked on unreachable hosts |25| Read timeout | Time to receive first/full response body | Fails on legitimate slow responses | Threads held waiting for hung services |26| Write timeout | Time to send request body | Fails on large uploads over slow links | Usually less critical than read timeout |2728### Timeout Rules2930- Always set explicit timeouts — never rely on defaults (which may be infinite)31- Set read timeout based on the slowest expected response from upstream32- Set connect timeout shorter than read timeout (connection should be fast; 1-5s typical)33- Write timeout matters primarily for large request bodies (file uploads, bulk data)34- Use shorter timeouts for non-critical calls35- Log timeout values at startup for debugging36- For downstream SLA compliance: set total timeout (connect + read) below your own SLA budget minus processing time3738---3940## 2. Error Handling Strategy4142### Error Classification4344| Exception Source | Cause | Action |45| ----------------------- | ------------------- | ---------------------- |46| HTTP response error | 4xx/5xx response | Map to domain error |47| Network/timeout error | Connection failure | Retry or circuit break |48| Parsing/decoding error | Malformed response | Log and fail |4950### Error Handling Rules5152- Never let raw HTTP client exceptions propagate to callers53- Wrap in domain-specific exceptions (e.g., `PaymentApiException`)54- Log response status and body on errors (but mask sensitive data)55- Distinguish between retryable (network, 503) and non-retryable (400, 404) errors5657---5859## 3. Retry Strategy6061### Retry Decision Table6263| Scenario | Retry | Max Attempts | Backoff |64| ------------------ | ----- | ------------ | -------------------- |65| Network timeout | Yes | 3 | Exponential 2x |66| Connection refused | Yes | 3 | Exponential 2x |67| HTTP 429 | Yes | 3 | Respect Retry-After |68| HTTP 503 | Yes | 3 | Exponential 2x |69| HTTP 4xx (not 429) | No | - | - |70| Parsing error | No | - | - |7172### Exponential Backoff with Jitter7374Exponential backoff prevents overwhelming a recovering service. Jitter prevents synchronized retry storms (thundering herd).7576```text77Base formula:78 delay = min(base * 2^attempt, max_delay)7980Full jitter (recommended — distributes retries evenly):81 delay = random(0, min(base * 2^attempt, max_delay))8283Equal jitter (balanced between spread and minimum wait):84 temp = min(base * 2^attempt, max_delay)85 delay = temp/2 + random(0, temp/2)8687Example with base=1s, max=30s:88 Attempt 1: random(0, 2s)89 Attempt 2: random(0, 4s)90 Attempt 3: random(0, 8s)91```9293Reference: AWS Architecture Blog — [Exponential Backoff and Jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/)9495### Retry Principles9697- Always use exponential backoff with jitter to avoid thundering herd98- Prefer full jitter over equal jitter or decorrelated jitter for most use cases99- Set a maximum retry count — never retry indefinitely100- Set a retry budget (e.g., max 20% of requests can be retries) to prevent retry amplification across service layers101- Do not retry inside a database transaction — retry at the outermost layer102- Do not retry non-idempotent requests without an idempotency key103- Ensure retried requests are safe: check that the upstream API is idempotent or use idempotency keys104105---106107## 4. Circuit Breaker Pattern108109### When to Use110111- External APIs with unpredictable latency or availability112- Non-critical dependencies where degraded operation is acceptable113- High-throughput paths where cascading failures are a risk114115### Key Parameters116117| Parameter | Description | Typical Value |118| ---------------------------- | ---------------------------------------- | ------------- |119| Sliding window size | Number of calls to evaluate | 10 |120| Failure rate threshold | Percentage to open circuit | 50% |121| Wait duration in open state | Cooldown before half-open | 30s |122| Half-open permitted calls | Test calls before closing | 3 |123| Slow call duration threshold | What counts as "slow" | 5s |124| Slow call rate threshold | Percentage of slow calls to open circuit | 80% |125126---127128## 5. Response Mapping Principles129130### DTO Separation131132- Never expose external API DTOs to internal service layers133- Map external responses to domain models at the client boundary134- Handle missing/null fields defensively in external DTOs135136### Mapping Example137138```text139External API Response (their naming) Domain Model (our naming)140───────────────────────────────────── ──────────────────────────141user_id: String → id: String142full_name: String → name: String143email_address: String → email: String144```145146---147148## 6. Connection Pooling149150### Why Connection Pooling Matters151152Creating a new TCP connection (and TLS handshake) for every request adds 50-200ms of latency. Connection pools maintain reusable connections to amortize this cost.153154### Pool Configuration Parameters155156| Parameter | Description | Recommendation |157| --- | --- | --- |158| Max connections per host | Max concurrent connections to a single destination | 20-50 (match downstream capacity) |159| Max total connections | Total connections across all hosts | 100-200 for multi-host clients |160| Idle timeout | How long an idle connection is kept alive | 60-90s (match server/LB keep-alive) |161| Max connection lifetime | Max age of a connection regardless of activity | 5-10 minutes |162| Connection acquisition timeout | Max wait time when pool is exhausted | 5s (fail fast) |163164### Connection Pool Pitfalls165166| Pitfall | Consequence | Fix |167| --- | --- | --- |168| Pool too small for traffic | Requests queue up waiting for connections | Size pool based on peak concurrent requests per host |169| Pool too large | Wastes server resources, may exceed upstream connection limits | Right-size based on actual concurrency needs |170| No idle timeout | Stale connections cause errors | Set idle timeout shorter than server keep-alive |171| No max lifetime | DNS changes not picked up | Set max lifetime to force periodic reconnection |172| One pool for all APIs | Slow API exhausts pool, blocking fast APIs | Use separate pools (or separate clients) per downstream API |173| Ignoring keep-alive | Server closes connection but client reuses it | Respect `Connection: close` header, match keep-alive settings |174175### Sizing Guidelines176177```text178Pool size per host = peak_concurrent_requests_to_that_host * 1.5179180Example:181 - Your service handles 1000 req/s182 - 30% of requests call Payment API (300 req/s)183 - Payment API avg response time: 200ms184 - Concurrent connections needed: 300 * 0.2 = 60185 - Pool size: 60 * 1.5 = 90186```187188---189190## 7. Anti-Patterns191192- No timeout configuration (risk of thread exhaustion)193- Retrying non-idempotent requests (POST without idempotency key)194- Logging full response bodies in production (performance + data leak)195- Sharing a single HTTP client instance for unrelated APIs with different latency profiles196- Calling external APIs inside database transactions197- Ignoring rate limit headers from upstream APIs198- No circuit breaker on critical external dependencies199- Not setting connection pool idle/max lifetime (stale connections, DNS caching issues)200- Retry amplification across service layers (service A retries 3x, calls service B which retries 3x = 9 attempts)201202## 8. Related Skills203204- `error-handling`: HTTP error response handling strategies205- `observability`: HTTP client metrics collection and monitoring206- `security`: API authentication, TLS, header security207- `caching`: HTTP response caching strategies208209## Additional References210211- For circuit breaker, retry, timeout, and bulkhead resilience patterns, see [references/resilience-patterns.md](references/resilience-patterns.md)212- For Spring Boot implementation patterns (RestClient, Spring Retry, Resilience4j), see `spring-framework` skill — [references/http-client.md](../spring-framework/references/http-client.md)