A stable replay key lets a remote handler identify two attempts as one logical command when it enforces that key.
Spring HTTP replay key: two requests, one modeled reservation
Repeat the same command
After the first timed-out POST, RestClientBoundaryTest releases the delayed response and sends another POST with the same Idempotency-Key. The local server sees two HTTP requests, returns reservation-2048 and records one write. The test checks both counters and the stored key. This is a server-enforced behavior; RestClient does not provide deduplication by itself.
The key belongs to the normalized command and tenant. The tenant-scoping lesson explains why the same opaque text used by a different tenant must not select another tenant’s result. A key reused for changed request contents should be rejected rather than returning the first response.
Draw the boundary around the model
The loopback handler uses a concurrent in-memory set, not a database transaction. It has no TTL, payload hash, failure-after-reservation recovery or protection across two server replicas. The test also sends attempts sequentially; it does not race two first requests against the same key.
A production remote service must store the key and result atomically with the mutation, specify retention, and let a caller retrieve the prior result after an uncertain response. The timeout trace explains why this is necessary.
Checked source
// The second POST reuses tenant-east-receipt-2048.
assertEquals(2, requests.get());
assertEquals(1, writes.get());
assertEquals(Set.of("tenant-east-receipt-2048"), committedKeys);Verification boundary
RestClientBoundaryTest.aTimedOutWriteMayHaveCommittedBeforeTheCallerRetries.
Costs and limits
Sequential local requests and one in-memory remote process are checked. Durable replay, TTL, changed payloads and concurrent first-use races remain open.
Common Mistakes
- Do not call a header a deduplication guarantee unless the server enforces it.
- Do not reuse one key for a changed command.
- Do not claim a sequential set test proves a distributed race.
Read next
Spring HTTP timeout after POST: the write may already exist, Spring POST idempotency keys: bind replay to tenant and command, Spring POST idempotency keys: replay the same receipt result, Spring HTTP retries: only replay a command with a defined identity.
