Guaranteed exactly-once execution across microservices. Prevent duplicate side-effects, acquire concurrent
in-flight locks, detect payload tampering, and replay byte-perfect HTTP responses based on the IETF
Idempotency-Key specification.
Distributed locks alone (e.g. ShedLock or basic Redis locks) prevent concurrent execution, but fail to solve response replays or payload tampering.
| Distributed Execution Challenge | Distributed Locks Alone | AvoOnce Idempotency Engine |
|---|---|---|
| Dropped Responses / Retries | ❌ Lock releases after execution; client retries re-execute or fail | ✅ Caches & replays exact HTTP status codes, headers, and raw body bytes |
| Concurrent Duplicate Requests | ⚠️ Blocks or rejects; client retry does not receive cached result | ✅ Returns immediate 409 Conflict during execution, cached result on retry |
| Payload Tampering Protection | ❌ Same key with modified payload executes or corrupts database | ✅ SHA-256 body hash validation rejects modified payloads with 422 Unprocessable Entity |
| Framework Overhead & Scope | ⚠️ Requires complex custom aspects or manual lock boilerplate | ✅ Clean, selective @Idempotent annotation with zero overhead for unannotated routes |
Designed with pure Java SPI, high-throughput caching, and multi-framework support.
Explicit, annotation-driven protection using @Idempotent. Unannotated
health checks, metrics, and read endpoints bypass filtering with zero performance cost.
Captures exact HTTP status code, response headers, and raw response body bytes. Replays cached responses without triggering controller logic or database queries.
Prevents race conditions when clients send parallel duplicate requests. In-flight
requests holding the lock receive an instant 409 Conflict response.
Computes cryptographic request hashes. Rejects requests trying to reuse an existing key
with altered parameters with a 422 Unprocessable Entity.
Transient server errors (HTTP 5xx) automatically transition state to
FAILED, releasing the key so clients can safely retry execution.
Switch seamlessly between single-node Caffeine in-memory, distributed JDBC (PostgreSQL, MySQL, H2, Oracle, MariaDB, SQL Server), and distributed Redis.
Choose your preferred framework baseline below.
<dependency>
<groupId>io.github.ravocode.avoonce</groupId>
<artifactId>idempotency-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>io.github.ravocode.avoonce</groupId>
<artifactId>idempotency-caffeine</artifactId>
<version>1.0.0</version>
</dependency>
import io.github.ravocode.avoonce.spring.annotation.Idempotent;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
@PostMapping
@Idempotent // Protected by AvoOnce
public ResponseEntity<PaymentResponse> createPayment(@RequestBody PaymentRequest req) {
return ResponseEntity.status(201).body(paymentService.process(req));
}
}
<dependency>
<groupId>io.github.ravocode.avoonce</groupId>
<artifactId>idempotency-jaxrs</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>io.github.ravocode.avoonce</groupId>
<artifactId>idempotency-caffeine</artifactId>
<version>1.0.0</version>
</dependency>
@ApplicationScoped
public class IdempotencyProducer {
@Produces @Singleton
public IdempotencyContainerFilter filter() {
return new IdempotencyContainerFilter(new CaffeineIdempotencyRepository(new IdempotencyConfig()));
}
}
@Path("/api/payments")
public class PaymentResource {
@POST
@Idempotent // Name-bound JAX-RS protection
public Response createPayment(PaymentRequest req) {
return Response.status(201).entity(paymentService.process(req)).build();
}
}
@Override
public void run(MyConfiguration config, Environment environment) {
IdempotencyRepository repository = new CaffeineIdempotencyRepository(new IdempotencyConfig());
// Register AvoOnce JAX-RS Name-Bound Filter
environment.jersey().register(new IdempotencyContainerFilter(repository));
}
import io.github.ravocode.avoonce.jaxrs.Idempotent;
import jakarta.ws.rs.*;
@Path("/api/orders")
public class OrderResource {
@POST
@Idempotent // Name-bound protection for Dropwizard
public Response placeOrder(OrderRequest request) {
return Response.ok(orderService.place(request)).build();
}
}
ResourceConfig resourceConfig = new ResourceConfig();
IdempotencyRepository repository = new CaffeineIdempotencyRepository(new IdempotencyConfig());
resourceConfig.register(new IdempotencyContainerFilter(repository));
resourceConfig.register(PaymentResource.class);
Clean separation between framework adapters, state machine, and pluggable storage engines.
The @Idempotent filter intercepts the request, extracts
Idempotency-Key header, and computes a SHA-256 body hash.
If cached payload hash mismatches → returns 422 Unprocessable Entity. If key
is currently IN_PROGRESS → returns 409 Conflict.
If lock acquired, controller executes. Response status, headers, and body bytes are stored
as COMPLETED for instant replay.
Server 5xx errors transition record to FAILED, allowing the
client to safely retry the request with the same key.
Pick the storage engine that matches your infrastructure topology.
Ultra-fast, zero-dependency in-memory store. Ideal for single-node applications or local testing with automated sliding-window eviction.
Multi-dialect support for PostgreSQL, MySQL, H2, Oracle, MariaDB, and SQL Server.
Features automatic table initialization (auto-ddl) and scheduled background cleanup.
High-throughput distributed store leveraging Redis native TTL expiration and zero-dependency binary record serialization for maximum performance.