Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring POST idempotency keys: replay the same receipt result

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

An idempotency key lets an API return the recorded result for a repeated create request instead of creating another receipt.

Download Spring source kit

Key and request must agree

The checked controller requires a nonblank Idempotency-Key. The first key and body create receipt-1. Sending the same key and body again returns receipt-1. Reusing the key with a different body gets 409; a new key can create receipt-2. The service compares exact request text in this fixture, so even harmless formatting differences count as different requests.

A real service should scope the key to the authenticated owner and operation, store a canonical request fingerprint and the response in durable storage, and perform the record/create decision atomically. A synchronized HashMap covers one controller instance only. It loses history on restart and cannot coordinate other replicas. Tenant write authorization is a separate check.

Do not confuse replay with preconditions

A retry of a create request asks whether the same operation already happened. If-Match asks whether a known resource revision is still current before an update. Both can appear in one API, but they protect different failure paths. A network timeout after a commit is exactly where replaying the stored result matters.

Checked source

Java
@PostMapping(path = "/contract/idempotent-receipts", consumes = "text/plain")
synchronized ResponseEntity<String> create(
    @RequestHeader(value = "Idempotency-Key", required = false) String key,
    @RequestBody String request) {
    if (key == null || key.isBlank()) return ResponseEntity.badRequest().build();
    Stored prior = accepted.get(key);
    if (prior != null) {
        if (!prior.request().equals(request)) return ResponseEntity.status(409).build();
        return ResponseEntity.ok(prior.receipt());
    }
    String receipt = "receipt-" + (accepted.size() + 1);
    accepted.put(key, new Stored(request, receipt));
    return ResponseEntity.ok(receipt);
}

Verification boundary

IdempotentReceiptPostTest.replayReturnsTheOriginalReceiptAndConflictingPayloadIsRejected runs in the downloadable Spring source kit. The excerpt leaves out surrounding setup and imports; the kit contains the complete test.

Costs and limits

Map lookup is expected O(1) average time within one JVM, but the synchronized method serializes calls and memory grows with retained keys. The test covers local HTTP mapping and response decisions, not durable atomic creation, expiry, authentication, distributed retries or a payment-grade API.

Common Mistakes

  • Do not mint another receipt for a retry with the same operation key.
  • Do not accept the same key with a different request silently.
  • Do not use a global key namespace when two tenants can choose the same key.

Read next

Spring If-Match writes: reject a stale receipt revision, Spring method authorization: reject a cross-tenant read, Spring transactional outbox: commit a receipt and event row together.

Continue with checked tenant commands

Continue with Spring POST idempotency keys: bind replay to tenant and command.

Release boundary continuation

Continue with Spring HTTP retries: only replay a command with a defined identity.

spring
spring-boot
idempotent-receipt-post
Storage details