A client sends a request to create an order. The server processes it, but the connection drops before the response reaches the client.
The client retries. How can the API recognise that retry and avoid creating another order?
This is the problem my open-source Spring Boot Idempotency Starter aims to address.
What is idempotency?
For an API operation, idempotency means that repeating the same logical request should not produce additional side effects.
An idempotency key lets the client identify that logical request. It should reuse the same key when retrying and generate a new key for a new operation.
What the starter provides
The starter supports:
- An
@Idempotentannotation for protected endpoints. - An
Idempotency-Keyrequest header. - Request body hash validation.
- Caching and replaying completed responses.
- In-memory and Redis storage.
- Configurable record expiry and processing timeout.
- Spring Boot auto-configuration.
Here is how to get started.
1. Add the Maven dependency
Requirements: Java 17+, Spring Boot 3.x, and Maven 3.8+.
<dependency>
<groupId>io.github.iroshperera</groupId>
<artifactId>spring-boot-idempotency-starter</artifactId>
<version>0.1.0</version>
</dependency>
2. Annotate an endpoint
Add @Idempotent to the endpoint you want to protect:
import lk.irosh.idempotency.annotation.Idempotent;
@Idempotent
@PostMapping("/orders")
public OrderResponse createOrder(
@RequestBody OrderRequest request
) {
return orderService.create(request);
}
This snippet belongs inside your controller. OrderRequest, OrderResponse, and orderService represent your application's own types and service.
3. Configure the starter
For a local example, add the following to application.yml:
idempotency:
enabled: true
storage: memory
required: true
header-name: Idempotency-Key
default-expiry: 24h
processing-timeout: 5m
cache-response: true
reject-different-request-hash: true
This configuration requires a key, enables response caching, and rejects reuse of a key with a different request body.
4. Send a request with a key
Assuming your endpoint is available at /orders and accepts the example payload:
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-request-001" \
-d '{"item":"Laptop","quantity":1}'
Retry the same request with the same key and body.
The documented behaviour is:
| Scenario | Behaviour |
|---|---|
| First request | Executes the controller and saves the response |
| Retry after completion, with the same key and body | Returns the saved response without executing the controller again |
| Same key with a different body | Returns 409 Conflict
|
| Missing key when a key is required | Returns 400 Bad Request
|
Use a fresh key when creating a genuinely new order.
Choosing a storage option
In-memory storage is a starting point for development, testing, and single-instance applications. Its records are local to the running application and do not survive a restart.
For shared records across application instances, the starter also provides Redis storage:
idempotency:
storage: redis
spring:
data:
redis:
host: localhost
port: 6379
Merge these settings into your configuration and make sure Redis is running.
Things to consider before production use
Idempotency records have a limited lifetime. Once a record expires, you should not assume that an old key will still prevent duplicate processing.
Shared storage is also only one part of a distributed solution. Concurrent requests, failures during processing, and the relationship between business transactions and stored responses need careful testing.
For critical operations, keep appropriate database constraints and business-level safeguards in place. An idempotency layer should not be treated as a blanket guarantee of exactly-once execution.
What’s next?
The project roadmap includes:
- JDBC and PostgreSQL storage.
- MongoDB storage.
- Distributed locking improvements.
- Metrics and monitoring.
- Additional integration tests.
Try it and share your feedback
The project is open source under the MIT License.
👉 View Spring Boot Idempotency Starter on GitHub
Feedback, bug reports, and contributions are welcome—especially around concurrency and failure scenarios.
How do you handle duplicate requests in your Spring Boot applications?
Top comments (0)