DEV Community

Dev Cookies
Dev Cookies

Posted on

๐Ÿš€ Designing Idempotent APIs in Spring Boot

In RESTful API design, idempotency is a crucial concept that ensures safety and consistency, especially for POST, PUT, or DELETE operations. It guarantees that repeating the same request multiple times has the same effect as making it once. This is essential in distributed systems where network retries are common.

In this blog, weโ€™ll explore:

  • โœ… What is idempotency?
  • ๐Ÿ” Why idempotency matters in API design
  • ๐Ÿ› ๏ธ How to implement idempotency in Spring Boot
  • ๐Ÿ” Using Idempotency-Key
  • ๐Ÿ’ก Best practices
  • ๐Ÿ“ฆ Example code snippets

๐Ÿค” What is Idempotency?

An API is idempotent if multiple identical requests result in the same server state and same response.

HTTP Method Idempotent? Description
GET โœ… Safe, no changes
PUT โœ… Overwrites the resource
DELETE โœ… Removes the resource (multiple calls have the same result)
POST โŒ (by default) Usually creates new resources (can be made idempotent)

๐Ÿ” Why Idempotency Matters

Imagine a money transfer API that retries due to a timeout. Without idempotency, it may deduct the amount multiple times. Idempotency ensures:

  • Data consistency
  • Retry safety
  • Easier debugging
  • Enhanced user trust

๐Ÿ› ๏ธ How to Design Idempotent APIs in Spring Boot

โœ… 1. Use Idempotency Key (For POST requests)

  • Clients send a unique Idempotency-Key in the header.
  • Server stores the response associated with the key.
  • Repeated requests with the same key return the stored response without executing the logic again.

๐Ÿ” Sample Header

POST /api/payments
Idempotency-Key: abc123
Enter fullscreen mode Exit fullscreen mode

๐Ÿงช Example Implementation in Spring Boot

Step 1: Create Entity for Storing Keys

@Entity
public class IdempotencyRecord {
    @Id
    private String key;
    private String responseBody;
    private int statusCode;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Repository

@Repository
public interface IdempotencyRepository extends JpaRepository<IdempotencyRecord, String> {}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create Filter or Interceptor

@Component
public class IdempotencyInterceptor implements HandlerInterceptor {

    @Autowired
    private IdempotencyRepository repository;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        String key = request.getHeader("Idempotency-Key");
        if (key != null && repository.findById(key).isPresent()) {
            IdempotencyRecord record = repository.findById(key).get();
            response.setStatus(record.getStatusCode());
            response.getWriter().write(record.getResponseBody());
            return false; // Stop processing
        }
        return true; // Continue
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Store Response After Processing

In your controller or service layer:

@PostMapping("/payments")
public ResponseEntity<String> makePayment(@RequestBody PaymentRequest request,
                                          @RequestHeader("Idempotency-Key") String key) {
    String response = "Payment of " + request.getAmount() + " successful!";

    // Save idempotent record
    IdempotencyRecord record = new IdempotencyRecord();
    record.setKey(key);
    record.setResponseBody(response);
    record.setStatusCode(200);
    repository.save(record);

    return ResponseEntity.ok(response);
}
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Caution

  • TTL (Time-to-live): Store keys temporarily to prevent DB bloat.
  • Uniqueness: Ensure Idempotency-Key is truly unique per action.
  • Side effects: Avoid non-idempotent side-effects like sending emails in retries.

๐Ÿง  Best Practices

  • Make APIs idempotent by default where possible.
  • Return consistent response codes.
  • Store metadata for debugging.
  • Educate API consumers on using Idempotency-Key.

๐Ÿงฉ Real-World Use Cases

  • Payment processing (Stripe, Razorpay)
  • Booking engines
  • Order creation
  • Email subscriptions

โœ… Conclusion

Designing idempotent APIs is a must for building robust, production-grade systems. It prevents data duplication, race conditions, and unexpected behaviorsโ€”especially in failure scenarios. With a simple interceptor and repository, you can build idempotent POST APIs in Spring Boot easily.

Top comments (0)