- Payment requests ๐ณ
- Order creation ๐
- Wallet top-ups ๐ฐ
- OTP / Subscription triggers ๐
If the client retries due to: โ Network timeout โ Load balancer retry โ Kafka redelivery โ User double-click โ API gateway automatic retry
โฆyou may charge twice or create duplicate orders ๐ฑ
So we must guarantee:
๐ Same request ID = Same result Even if executed multiple times
Let's implement this the correct microservices way ๐
๐ What Makes a POST Idempotent?
Client sends a unique Idempotency-Key HTTP header:
Idempotency-Key: cff23e65-bf11-4dfe-a601-a618735b45c2API checks: โ Key exists โ return stored result โ Key not found โ process & store result
๐๏ธ Step-by-Step Implementation
1๏ธโฃ Create an Idempotency Store (Redis recommended)
public interface IdempotencyRepository {
Optional<String> getResponse(String key);
void saveResponse(String key, String response);
}Redis Implementation
@Repository
public class RedisIdempotencyRepository implements IdempotencyRepository {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public Optional<String> getResponse(String key) {
return Optional.ofNullable(redisTemplate.opsForValue().get(key));
}
@Override
public void saveResponse(String key, String response) {
redisTemplate.opsForValue().set(key, response, Duration.ofHours(1));
}
}โ TTL prevents storage explosion โ Redis = atomic and super fast
2๏ธโฃ Service Layer Wrapper for Idempotency
@Service
public class IdempotencyService {
@Autowired
private IdempotencyRepository repo;
public ResponseEntity<?> execute(String key, Supplier<ResponseEntity<?>> action) {
return repo.getResponse(key)
.map(cached -> ResponseEntity.ok().body(cached))
.orElseGet(() -> {
ResponseEntity<?> result = action.get();
repo.saveResponse(key, result.getBody().toString());
return result;
});
}
}3๏ธโฃ Controller Example: "Create Payment"
@PostMapping("/payments")
public ResponseEntity<?> createPayment(
@RequestHeader("Idempotency-Key") String idempotencyKey,
@RequestBody PaymentRequest req) {
return idempotencyService.execute(idempotencyKey, () -> {
Payment payment = paymentService.process(req);
return ResponseEntity.ok(payment);
});
}๐งช Testing the Behavior
Send POST twice with same key:
curl -X POST http://localhost:8080/payments \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 999-111-ABC" \
-d '{"amount":100,"userId":"U1"}'๐ Result: First call = real processing ๐ Second call = cached response returned instantly โ Payment executed only once โจ
๐ Hardcore Production Concerns

Example Redis atomic lock:
boolean locked = redisTemplate.opsForValue()
.setIfAbsent("lock:"+key, "1", Duration.ofSeconds(10));
if (!locked) {
throw new RetryableException("Request in progress!");
}Best Practice Summary Checklist โ

Idempotency is not optional in event-driven microservices.
It protects users and money It keeps systems correct under retries It prevents duplicate side effects
๐ง Idempotency = Trust in distributed systems