A Java library providing idempotency utilities for Java applications. Supports both annotation-based (AOP) and programmatic service-based approaches.
- Annotation-based idempotency with Spring AOP
- Programmatic API for fine-grained control
- Extensible storage with multiple implementations
- Configurable behavior through properties
<dependency>
<groupId>io.github.arun0009</groupId>
<artifactId>idempotent-core</artifactId>
<version>${idempotent.version}</version>
</dependency>| Property | Default | Description |
|---|---|---|
idempotent.key.header |
X-Idempotency-Key |
HTTP header for idempotency key |
idempotent.inprogress.max.retries |
5 |
Max retries for in-progress requests |
idempotent.inprogress.retry.initial.intervalMillis |
100 |
Initial retry interval in ms |
idempotent.inprogress.retry.multiplier |
2 |
Exponential backoff multiplier |
No additional configuration needed. The in-memory store is used by default if no other store is configured.
For Redis configuration and customizations, please refer to the idempotent-redis README.
For DynamoDB configuration and customizations, please refer to the idempotent-dynamo README.
To implement a custom store, create a class that implements IdempotentStore and define it as a @Bean:
@Configuration
public class CustomIdempotentConfig {
@Bean
@Primary
public IdempotentStore customIdempotentStore() {
return new CustomIdempotentStore();
}
}@Idempotent(key = "#paymentDetails", duration = "PT1M", hashKey=true)
@PostMapping("/payments")
public PaymentResponse postPayment(@RequestBody PaymentDetails paymentDetails) {
// Method implementation - only executes once per unique paymentDetails
}// Basic usage with Duration
String result = idempotentService.execute("payment-123", () -> {
return processPayment("123");
}, Duration.ofMinutes(1));
// Or using ISO-8601 duration string
String result2 = idempotentService.execute("payment-123", "payment-process", () -> {
return processPayment("123");
}, Duration.parse("PT1M"));
}, 300);
// Different operations with same key
String email = idempotentService.execute("user-456", "send-email",
() -> sendWelcomeEmail("user-456"), Duration.ofMinutes(10));
// Advanced usage with IdempotentKey
IdempotentKey key = new IdempotentKey("order-789", "process-payment");
PaymentResult result = idempotentService.execute(key,
() -> paymentGateway.processPayment(order), Duration.ofMinutes(30));// Primitives
int count = idempotentService.execute("count",
() -> userRepo.count(), Duration.ofMinutes(1));
// Complex objects
Order order = idempotentService.execute("order-123",
() -> orderService.getOrder("123"), Duration.ofMinutes(5));
// Null values
String data = idempotentService.execute("optional",
() -> maybeGetData(), Duration.ofMinutes(2));Implement the IdempotentStore interface to use your preferred storage:
public class CustomIdempotentStore implements IdempotentStore {
// Implement required methods
}Then configure it in your Spring configuration:
@Configuration
public class IdempotentConfig {
@Bean
public IdempotentStore idempotentStore() {
return new CustomIdempotentStore();
}
@Bean
public IdempotentAspect idempotentAspect(IdempotentStore idempotentStore) {
return new IdempotentAspect(idempotentStore);
}
}- In-Memory:
InMemoryIdempotentStore(default) - Redis: idempotent-redis
- DynamoDB: idempotent-dynamo
- Failed operations are automatically cleaned up
- In-progress state is maintained during retries
- Configurable retry policies for concurrent requests
- Use
hashKey=truefor large objects to store hashes instead of serialized objects - Choose appropriate TTL values based on your use case
- Monitor storage usage for high-throughput applications
While idempotent-core provides the core functionality, you can use specific implementations like Redis or DynamoDB by including the respective modules (idempotent-redis, idempotent-dynamo). However, if you prefer to use a different storage solution, you can implement your own store as described above.