ediverse Explore the platform

Spotlight PEPPOL BIS Billing 3.0 The EU e-invoicing mandate is here — France Sept 2026, Belgium Jan 2026, Germany 2025.

Timeout

The explicit promise: "if no answer in X seconds, I give up". The pattern that prevents silent failures from blocking everything.

Problem

A hub service calls a partner's AS2 API to send an INVOIC. The partner is restarting its server. The TCP connection is open but no HTTP response arrives. Without timeout, the thread is blocked indefinitely. The default HTTP client (Java OkHttp, Apache HTTPClient, Node.js fetch) has no active timeout by default. After an hour, 200 threads are blocked, the pool is saturated, no new request goes through. The partner is dead, the hub too.

Forces

  • Unbounded waiting turns slowness into failure. The classic distributed network trap.
  • Network layers have different time scales. Connect = a few seconds, read = minutes depending on volume.
  • The right timeout depends on context. AS2 batch INVOIC tolerates 5 min; a validation REST API, 5 s.
  • The timeout is invisible to both sides. The partner keeps working server-side, the client has already given up — hence the importance of idempotency.

Solution

Define each timeout explicitly: connect (establish connection), read/socket (receive a byte without new send), request total (full round-trip). Configure each client, broker, middleware layer. Document per partner: Walmart accepts 30s, PEPPOL recommends 60s, Stellantis 120s. Measure real p95/p99 latencies and set the timeout at p99 + 50% margin. Log every timeout to distinguish partner outage from overly aggressive config.

Timeout stack for an outbound AS2 request:

  ┌─────────────────────────────────────────────┐
  │ 1. DNS lookup        : timeout    2 s       │
  │ 2. TCP connect       : timeout    5 s       │
  │ 3. TLS handshake     : timeout   10 s       │
  │ 4. HTTP request send : timeout   30 s       │
  │ 5. HTTP read body    : timeout   60 s       │
  │ 6. MDN ack receive   : timeout  120 s       │
  │ 7. Total round-trip  : timeout  180 s       │
  └─────────────────────────────────────────────┘

  Each bound is a local kill-switch. The total is
  the "partner contract" bound. Without 7, 1-6
  can stack up to 4 min on dead traffic.

EDI implementation

Concrete case: in Java/Spring, configure RestTemplate or WebClient with connectTimeout=5s, readTimeout=60s. On OkHttp: callTimeout=180s. For AS2: Mendelson, Sterling, OpenAS2 expose connection.timeout, read.timeout, mdn.timeout parameters. SFTP: JSCH with separate session.timeout + channel.timeout. Kafka producer: request.timeout.ms=30000, delivery.timeout.ms=120000. API Gateway (Kong): upstream timeouts per route. And crucial: server-side HTTP timeout (Tomcat connectionTimeout, Netty READ_TIMEOUT) to avoid being overloaded by a slow client (slow loris).

Anti-patterns

  • No timeout. The "default" value is often infinite. The classic trap.
  • One global timeout. timeout=30s for everything: a legitimately long large INVOIC is killed, and an instant REST call waits 30s on failure.
  • Timeout without idempotency. Client gives up at 10s, partner commits at 10.1s: duplicate created on retry (without idempotency).
  • Timeout too aggressive. Real p99 = 45s, timeout = 30s: retries too often, amplifies failures (retry storm).
  • Timeout without propagation. Service A calls B with timeout 30s, B calls C with timeout 60s: A gives up before B finishes. Propagate the timeout (deadline propagation).
  • Circuit Breaker — repeated timeouts open the circuit.
  • Retry + backoff — each retry has its own timeout.
  • Idempotency — indispensable because timeout ≠ failure: the request may have committed.
  • Bulkhead — without timeout, compartment isolation is not enough.
  • Dead Letter Channel — destination for messages whose retries have all timed out.

Sources