Idempotency and Safe Retries
Idempotency determines whether repeating a request preserves the intended outcome rather than creating additional effects. Safe retries combine idempotent operation design with appropriate retry conditions, backoff, and failure handling.
An idempotent operation can be executed multiple times with the same intended effect as executing it once. In HTTP, this property concerns the intended state change, not necessarily identical responses, logs, metrics, or other observable side effects.
Retries are necessary because a client may not know whether a request reached the server or whether the response was lost. Retrying a non-idempotent operation can therefore create duplicates, such as charging a customer twice or creating two orders.
For operations that are naturally idempotent, such as replacing a resource with `PUT` or removing a resource with `DELETE`, repeating the request can generally converge on the same resource state. A `POST` operation is not inherently idempotent, but an application can make a particular `POST` workflow retry-safe with an idempotency key and server-side deduplication.
A robust retry policy should retry only failures that are plausibly transient, use bounded exponential backoff with jitter, and respect request deadlines. The server must retain enough information about an idempotency key to recognize a duplicate and return the appropriate prior result or otherwise prevent the operation from being applied twice.
In Java backend systems, idempotency often spans the HTTP layer, persistence layer, and external dependencies. Database uniqueness constraints, transactional state changes, durable idempotency records, and carefully designed message-processing semantics can provide stronger protection than relying on client behavior alone.
Key points
- Idempotency means repeated execution has the same intended effect as one execution.
- A lost response can make a client unsure whether the server already processed the request.
- HTTP method semantics provide useful defaults, but application-level operations can differ.
- Idempotency keys can make non-idempotent workflows such as order creation safely retryable.
- Retries should be bounded, use backoff and jitter, and target transient failures.
- Durable server-side deduplication is needed when duplicate execution would be harmful.
Interview questions
What does idempotency mean in HTTP?
An HTTP operation is idempotent when making the same request multiple times has the same intended server-side effect as making it once. This does not require every response or incidental side effect to be identical. For example, replacing a resource with `PUT` can be repeated without progressively changing the resource. Idempotency is especially important when clients may retry after network failures.
Why can retrying a request create duplicate business operations?
A client can lose the response even though the server successfully completed the operation. If the client then retries a non-idempotent request, the server may execute the business operation again. For example, an order-creation request could create two orders. The client therefore needs a way to distinguish a retry of an existing logical operation from a new operation.
How would you implement an idempotency key in a Java backend?
The client sends a unique idempotency key for one logical operation. The server stores that key with the operation status and result, using a durable store with a uniqueness constraint so concurrent requests cannot both create the operation. A duplicate key should return the previously recorded result when the original operation completed, while an in-progress operation needs a defined concurrency policy. The record also needs an appropriate retention period and semantics for key reuse.
Which failures should an HTTP client retry?
A client should generally retry failures that are plausibly transient, such as some connection failures, timeouts, or temporary server overload. It should avoid blindly retrying permanent failures such as malformed requests or failed authorization. The retry policy should also consider whether the operation is idempotent or protected by an idempotency mechanism. Exponential backoff, jitter, and a maximum retry budget help prevent retry storms.
How do you make an idempotent operation safe across multiple application instances?
The deduplication state must be shared through durable storage rather than held in a single process. A database uniqueness constraint or equivalent atomic operation can prevent two instances from accepting the same idempotency key concurrently. The business mutation and the idempotency state should be coordinated so that crashes cannot leave the system reporting success without the intended effect, or vice versa. For more complex workflows, an outbox or transactional messaging pattern may be needed to coordinate downstream effects.