MCPcopy Index your code
hub / github.com/arun0009/idempotent

github.com/arun0009/idempotent @v3.0.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v3.0.0 ↗ · + Follow
427 symbols 1,025 edges 92 files 67 documented · 16%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Idempotent

Maven Central License: MIT Java Spring Boot

Run once. Same result on every retry. A lightweight Spring Java library that makes any method or service safe to retry — not just HTTP APIs. Duplicate calls return the cached result; concurrent callers wait for the first run instead of racing.

Annotate a controller, a @Service, or a batch job. Or call IdempotentService directly. Pick Redis, DynamoDB, NATS KV, or JDBC — or start with the built-in in-memory store.

Idempotent

@Idempotent(key = "#order.id", duration = "30m")
@PostMapping("/orders")
public Receipt place(@RequestBody Order order) {
  return paymentGateway.charge(order);    // runs once, even on a retry storm
}

No bespoke locks. No “did this already happen?” checks. No duplicate work.

Why your service needs Idempotent

In a distributed system, the same request can arrive twice — or twenty times. Networks time out, clients retry, load balancers replay, queues redeliver. Without idempotency, you ship the order twice, charge the card again, or corrupt state.

Idempotent ties every retry to one logical operation: same key → same outcome. It claims the key atomically, runs your code once, caches the result, and coordinates any concurrent callers — across Redis, DynamoDB, NATS KV, or your SQL database.

You add a dependency and an annotation. The library handles the hard parts.

What you get

Two entry points, one engine @Idempotent on any Spring method, or IdempotentService.execute(...) from @Service classes, batch jobs, or message consumers.
Atomic key claims Native primitives on every backend (SET NX, conditional PutItem, revision CAS, INSERT) — two callers cannot both think they were first.
Self-healing expiry Per-entry TTL, native backend TTL where available, lazy delete on read, scheduled cleanup for SQL. No zombie keys.
Keys your way Client header (X-Idempotency-Key) or server-side SpEL (#user.id), with optional SHA-256 hashing.
Honest semantics Domain exceptions propagate as-is. Null/void results are cached. Non-2xx ResponseEntity is removed so the client can retry.

How it works

Client sends the same request twice (timeout, retry, replay):

POST /orders        X-Idempotency-Key: 1f3c1a93-…-3bd4
POST /orders        X-Idempotency-Key: 1f3c1a93-…-3bd4   ← duplicate
Scenario What happens
First call Claim key (IN_PROGRESS) → run your code → store COMPLETED
Duplicate while in progress Poll with exponential backoff → return the same response
Duplicate after success Cache hit — your method body does not run
Operation throws Entry removed, exception propagates, retry allowed
Entry expired Removed on next read; a new caller can claim the key

Set duration longer than your slowest operation, so an entry is never evicted mid-flight.

Without AOP

Same engine from any code — useful in @Service classes, consumers, or jobs:

return idempotentService.execute(
    "ord_42", "create-order", Order.class,
    () -> orderService.create(request),
    Duration.ofMinutes(30));

Choose your storage

In-memory is the default. Pick a persistent backend for production:

Module Best for Key primitive
Redis Sub-ms reads, native TTL, already in your stack SET NX / SET XX
DynamoDB AWS-native, managed scale, table TTL Conditional PutItem
NATS KV KV co-located with your JetStream cluster create + revision CAS
RDS / JDBC Postgres, MySQL, MariaDB, or H2 you already run INSERT + scheduled cleanup

All four share the same serialization, expiry, and state machine — swap backends without changing behavior.

Install

Add a storage module (it pulls in idempotent-core):

<dependency>
  <groupId>io.github.arun0009</groupId>
  <artifactId>idempotent-redis</artifactId> 
  <version>${idempotent.version}</version>
</dependency>

For @Idempotent, also add spring-boot-starter-aop. IdempotentService works without it.

API reference, configuration, and custom stores: idempotent-core.

Contributing

Issues and pull requests welcome at github.com/arun0009/idempotent. Released under the MIT License.

Extension points exported contracts — how you extend this code

IdempotentStore (Interface)
Persistence contract for idempotent entries. Lifecycle #store store performs a strict inser [5 implementers]
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/persistence/IdempotentStore.java
IdempotentPayloadCodec (Interface)
Serializes/deserializes idempotent payload values across storage backends. [4 implementers]
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/serialization/IdempotentPayloadCodec.java
IdempotentOperation (Interface)
Operation executed by IdempotentService#executeThrowable. Permits any Throwable, so callers such as the [1 implementers]
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/service/IdempotentOperation.java
IdempotentJsonMapperCustomizer (Interface)
Customizes the JsonMapper used by the default JSON payload codec.
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/serialization/IdempotentJsonMapperCustomizer.java

Core symbols most depended-on inside this repo

execute
called by 62
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/service/IdempotentOperation.java
getValue
called by 35
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/persistence/IdempotentStore.java
update
called by 28
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/persistence/IdempotentStore.java
store
called by 22
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/persistence/IdempotentStore.java
processName
called by 9
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/aspect/IdempotentAspect.java
serializeToString
called by 8
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/serialization/IdempotentPayloadCodec.java
detect
called by 7
idempotent-rds/src/main/java/io/github/arun0009/idempotent/rds/RdsDialect.java
deserializeFromString
called by 7
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/serialization/IdempotentPayloadCodec.java

Shape

Method 309
Class 110
Enum 4
Interface 4

Languages

Java100%

Modules by API surface

idempotent-rds/src/test/java/io/github/arun0009/idempotent/rds/RdsIdempotentStoreH2Test.java16 symbols
idempotent-dynamo/src/test/java/io/github/arun0009/idempotent/dynamo/DynamoIdempotentServiceIntegrationTest.java16 symbols
idempotent-nats/src/test/java/io/github/arun0009/idempotent/nats/NatsIdempotentServiceTest.java14 symbols
idempotent-nats/src/test/java/io/github/arun0009/idempotent/nats/NatsIdempotentControllerTest.java14 symbols
idempotent-dynamo/src/main/java/io/github/arun0009/idempotent/dynamo/IdempotentItem.java14 symbols
idempotent-nats/src/test/java/io/github/arun0009/idempotent/nats/NatsTestApplication.java13 symbols
idempotent-core/src/test/java/io/github/arun0009/idempotent/core/service/IdempotentServiceTest.java12 symbols
idempotent-redis/src/test/java/io/github/arun0009/idempotent/redis/RedisIdempotentServiceIntegrationTest.java11 symbols
idempotent-nats/src/test/java/io/github/arun0009/idempotent/nats/NatsAuthTest.java11 symbols
idempotent-core/src/main/java/io/github/arun0009/idempotent/core/aspect/IdempotentAspect.java10 symbols
idempotent-nats/src/main/java/io/github/arun0009/idempotent/nats/NatsIdempotentProperties.java9 symbols
idempotent-core/src/test/java/io/github/arun0009/idempotent/core/serialization/JacksonIdempotentPayloadCodecTest.java9 symbols

For agents

$ claude mcp add idempotent \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page