Implementing Idempotency Logic
1. Understanding Idempotent Operations
| Concept | Detail |
|---|---|
| Definition | Same effect regardless of repeats |
| Naturally idempotent | GET, DELETE, PUT |
| Needs key | POST creating resource |
| Why critical | Network retries, at-least-once delivery |
2. Implementing Idempotency Key Validation
| Validation | Detail |
|---|---|
| Header | Idempotency-Key |
| Format | UUID or opaque ≤ 255 chars |
| Required for | POST mutations |
| Reject if | Empty / malformed |
3. Implementing Idempotency Key Storage
| Field | Detail |
|---|---|
| key | Unique by (tenant, key, route) |
| request_hash | Detect mismatched body reuse |
| status | in_progress / completed |
| response | Cached response payload |
| created_at, expires_at | TTL (24h typical) |
4. Implementing Idempotent POST Handler
Example: Handler flow
public Response handle(String key, Request req) {
var hash = sha256(req.canonicalJson());
var existing = store.findByKey(key);
if (existing.isPresent()) {
if (!existing.get().requestHash().equals(hash))
throw new ConflictException("idempotency key reused with different body");
if (existing.get().status() == IN_PROGRESS) throw new ConflictException("in flight");
return existing.get().response(); // replay
}
store.save(new Entry(key, hash, IN_PROGRESS));
var resp = doWork(req);
store.complete(key, resp);
return resp;
}
5. Implementing Response Caching
| Aspect | Detail |
|---|---|
| Cached | Status code, headers, body |
| Storage | Redis with TTL |
| Replay | Identical to original |
| Indicate | Add header Idempotent-Replayed: true |
6. Implementing Retry-Safe Operations
| Strategy | Detail |
|---|---|
| Upsert by natural key | INSERT ... ON CONFLICT |
| Idempotency key in DB | Unique constraint |
| Compensating action | Detect duplicate and reverse |
7. Implementing Idempotency Token Expiration
| TTL | Use Case |
|---|---|
| 24 hours | General API |
| 7 days | Payments (Stripe) |
| After expiry | Treat as new request |
8. Implementing Natural Idempotency
| Pattern | Example |
|---|---|
| Set state | PUT user.status = "active" |
| Check then act | If not exists, create |
| Unique business key | order_number constraint |
9. Handling Concurrent Idempotent Requests
| Mechanism | Detail |
|---|---|
| Insert with unique constraint | First wins; others see existing |
| Distributed lock | SET NX in Redis |
| Status flag | Return 409 if in_progress |
10. Implementing Idempotency Audit Trail
| Log | Detail |
|---|---|
| key + actor | Who used the key |
| replay count | Detect retry storms |
| conflict events | Different body for same key |
11. Implementing Idempotency Middleware
Example: Spring filter
public class IdempotencyFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
if (!"POST".equals(req.getMethod())) { chain.doFilter(req, res); return; }
var key = req.getHeader("Idempotency-Key");
if (key == null) { chain.doFilter(req, res); return; }
// ... wrap response, check store, replay or proceed
}
}
12. Implementing Payment Idempotency
| Practice | Detail |
|---|---|
| Mandatory key | Reject without one |
| Long TTL | ≥ 24h, often 7 days |
| Provider idempotency | Forward to Stripe/PayPal idempotency-key |
| Audit immutable | Never overwrite payment records |