REST principles and idempotency — which methods are idemp… — Cracked Java
// High-Level Design (HLD / Distributed Systems) · API Design — REST, gRPC, GraphQL, WebSockets
MidSystem DesignEPAM

REST principles and idempotency — which methods are idempotent and why it matters for retries.

REST principles and idempotency — which methods are idempotent, and why it matters for retries

REST in one breath

REST models the domain as resources addressed by URLs, manipulated through a small fixed set of HTTP methods, with status codes carrying outcome and representations (usually JSON) carrying state. It is stateless — each request carries everything the server needs, so any replica can handle it — and responses are explicit about cacheability. The discipline that separates a senior answer is using HTTP semantics correctly rather than tunneling every operation through POST /doThing.

Safe vs idempotent — two different properties

  • Safe — the method has no side effects; it only reads. GET, HEAD, OPTIONS.
  • Idempotent — making the request N times has the same server-side effect as making it once. The response body may differ (e.g. a second DELETE returns 404), but the state converges to the same place.

Safe implies idempotent, but not vice versa: a PUT mutates yet is idempotent.

MethodSafeIdempotentWhy
GET / HEADyesyesread-only
PUTnoyesreplaces the resource with the supplied representation — same result every time
DELETEnoyesresource ends up gone regardless of how many times you call it
POSTnonocreates a new subordinate resource each time → duplicates
PATCHnonot guaranteeddepends on the patch: set field = x is idempotent; increment by 1 is not

Why this matters for retries

In a distributed system, a client that sends a request and gets no response cannot distinguish three cases: the request was lost, it succeeded but the response was lost, or it's still in flight. The only safe recovery is to retry — and retries are baked into load balancers, service meshes, gRPC clients, and SDKs.

If the method is idempotent, retrying is free: replay it until you get an answer. If it is not (a bare POST that charges a card or creates an order), a blind retry can double-charge or double-create.

The fix: idempotency keys

For an inherently non-idempotent operation, make it idempotent at the application layer with a client-generated idempotency key:

POST /payments
Idempotency-Key: 9f2c1e7a-...   (a UUID the client picks once per intent)
{ "amount": 4200, "currency": "USD" }

The server stores the key with the result of the first execution. On a retry with the same key, it returns the stored result instead of re-executing. Stripe's API is the canonical example.

An idempotency key turns a retried POST into a safe replay

Practical notes

  • Scope the key with a TTL (e.g. 24h) and key it per endpoint + user to avoid collisions.
  • GET should never mutate — putting a side effect behind a GET breaks caches, prefetchers, and crawlers that replay it freely.
  • Make DELETE and PUT genuinely idempotent; returning 404 on a second DELETE is fine — the state (gone) is unchanged.

Mark your status