Skip to content

Idempotency

Definition

Idempotency Definition


HTTP Methods and Idempotency

HTTP Methods and Idempotency


Implementing Idempotency

// Method 1: Idempotency Key
@RestController
public class PaymentController {

    @PostMapping("/payments")
    public ResponseEntity<Payment> createPayment(
            @RequestBody PaymentRequest request,
            @RequestHeader("Idempotency-Key") String idempotencyKey) {

        // Check if already processed
        Optional<Payment> existing = paymentRepo
            .findByIdempotencyKey(idempotencyKey);

        if (existing.isPresent()) {
            // Return same result - don't process again
            return ResponseEntity.ok(existing.get());
        }

        // Process payment
        Payment payment = paymentService.process(request);
        payment.setIdempotencyKey(idempotencyKey);
        paymentRepo.save(payment);

        return ResponseEntity.status(201).body(payment);
    }
}

// Method 2: Conditional Updates (Optimistic Locking)
public class AccountService {

    public void updateBalance(String accountId, BigDecimal newBalance,
                             long expectedVersion) {
        int updated = jdbcTemplate.update(
            "UPDATE accounts SET balance = ?, version = version + 1 " +
            "WHERE id = ? AND version = ?",
            newBalance, accountId, expectedVersion
        );

        if (updated == 0) {
            throw new ConcurrentModificationException();
        }
    }
}

// Method 3: Natural Idempotency Key
public class OrderService {

    // Using order ID makes create idempotent
    public Order createOrder(String orderId, OrderRequest request) {
        // If order exists, return existing
        return orderRepo.findById(orderId)
            .orElseGet(() -> {
                Order order = new Order(orderId, request);
                return orderRepo.save(order);
            });
    }
}

Idempotency in Distributed Systems

Idempotency in Distributed Systems


Database Idempotency

// Pattern 1: UPSERT (INSERT ... ON CONFLICT)
public class UserRepository {

    public void saveOrUpdate(User user) {
        // PostgreSQL
        jdbcTemplate.update("""
            INSERT INTO users (id, email, name)
            VALUES (?, ?, ?)
            ON CONFLICT (id) DO UPDATE
            SET email = EXCLUDED.email, name = EXCLUDED.name
        """, user.getId(), user.getEmail(), user.getName());
    }
}

// Pattern 2: Conditional INSERT
public class EventRepository {

    public boolean saveIfNotExists(Event event) {
        try {
            jdbcTemplate.update(
                "INSERT INTO events (id, type, data) VALUES (?, ?, ?)",
                event.getId(), event.getType(), event.getData()
            );
            return true;  // New event saved
        } catch (DuplicateKeyException e) {
            return false;  // Already exists
        }
    }
}

// Pattern 3: Compare-and-Swap
public class InventoryService {

    public boolean decrementStock(String productId, int quantity,
                                  int expectedStock) {
        int updated = jdbcTemplate.update("""
            UPDATE inventory
            SET stock = stock - ?
            WHERE product_id = ? AND stock = ?
        """, quantity, productId, expectedStock);

        return updated > 0;  // True if successful
    }
}

Non-Idempotent to Idempotent

Converting Non-Idempotent Operations


Idempotency Key Best Practices

Idempotency Key Best Practices


Tips & Tricks

Tips and Tricks