advanced~2h

Idempotency in Distributed Systems

Retry (from two modules back) quietly assumed something this module finally makes explicit: that calling an operation twice is safe. This module is what makes that actually true.

Learning objectives

  • Beginner: Define idempotency in one sentence and give an example of a naturally idempotent operation.
  • Intermediate: Implement an idempotency key so a duplicate request doesn't double-process.
  • Advanced: Explain why idempotency matters even MORE in an at-least-once message-delivery system like Kafka.

◆ The problem

A network call can fail in a way where the REQUEST succeeded on the server but the RESPONSE never made it back to the caller — from the caller's perspective, this is indistinguishable from the request never arriving at all, so a naive retry is a completely reasonable thing to attempt. If that operation was "charge this card $50," retrying it means charging the customer twice for one purchase.

An operation is idempotent if calling it once produces the exact same end result as calling it two, three, or a hundred times — setStatus(SHIPPED) is naturally idempotent (calling it repeatedly always leaves status as SHIPPED); incrementStock(-1) is NOT (calling it twice decrements twice).

Making an inherently non-idempotent operation (like "charge this card") SAFE to retry means the CALLER attaches a unique idempotency key to the request, and the SERVER remembers which keys it has already processed — a retried request with the same key returns the ALREADY-COMPLETED result instead of processing the charge a second time.

@PostMapping("/charge") public ChargeResult charge(@RequestHeader("Idempotency-Key") String key, @RequestBody ChargeRequest req) { Optional<ChargeResult> existing = idempotencyStore.get(key); if (existing.isPresent()) return existing.get(); // already processed — return the same result again ChargeResult result = paymentProcessor.charge(req); idempotencyStore.save(key, result); return result; }

💻 Code example

@PostMapping("/charge") public ChargeResult charge(@RequestHeader("Idempotency-Key") String key, @RequestBody ChargeRequest req) { Optional<ChargeResult> existing = idempotencyStore.get(key); if (existing.isPresent()) return existing.get(); ChargeResult result = paymentProcessor.charge(req); idempotencyStore.save(key, result); return result; }

◆ Under the hood

The Kafka & Microservices category already covers this from the messaging side: Kafka's SAFEST delivery guarantee without extra configuration is at-least-once, meaning a consumer can genuinely receive and process the SAME message more than once (after a rebalance, or a crash before committing an offset). A Kafka consumer writing to a database MUST be idempotent for exactly this reason — this module's pattern and that category's consumer-idempotency discussion are the same concept, viewed from the messaging side versus the HTTP side.

✓ Quick recap

  • An operation is idempotent if repeating it produces the same end result as doing it once — not every operation naturally is.
  • The idempotency-key pattern makes a non-idempotent operation safely retryable: the server remembers which keys it already processed and returns the same result again instead of reprocessing.
  • Retry (this category) and at-least-once Kafka delivery (Kafka & Microservices category) both DEPEND on idempotency being deliberately designed in — it's not automatic.

Want a visual for this concept?

Generate a diagram tailored to “Idempotency in Distributed Systems” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Capstone — A Resilient Microservices System← Back to all Spring Cloud chapters