🎉 Idempotent 3.0.0 — Run Once. Same Result on Every Retry.
3.0.0 is a major breaking release: cleaner Spring-native integration, a consistent store contract across every backend, and stronger, more predictable runtime semantics.
Full upgrade details live in
docs/MIGRATION.md. The highlights below cover everything that can change your build.
🚨 Biggest Change: Redis Integration Reworked
Redis now uses Spring-native wiring end-to-end instead of managing its own connections.
- Uses your application’s
RedisConnectionFactory(Lettuce, Jedis, or any Spring Data Redis driver) - Configure Redis via standard
spring.data.redis.* - Redis module config is now just
idempotent.redis.enabled(defaulttrue) - Removed all idempotent-specific Redis connection properties:
idempotent.redis.standalone.*,cluster.*,sentinel.*,auth.*,ssl.*
- Redis serialization now uses the shared
IdempotentPayloadCodec(same Jackson setup as core), replacingenableUnsafeDefaultTyping()
Store Contract — Now Explicit and Aligned
IdempotentStore has a single, documented contract across all backends:
store(...)is a strict insert — an existing key throwsIdempotentKeyConflictExceptionupdate(...)is a no-op when the key is missing (never resurrects a removed/expired entry)- Stores implement
loadValue(key, returnType)as a raw read - Default
getValue(...)in core applies expiry + best-effort lazy cleanup, so every backend behaves the same - Expiry is evaluated against wall-clock time (
expiresAt); keep nodes NTP-synced and size TTLs above your slowest op plus expected skew
Runtime Semantics
IdempotentAspectnow delegates the full state machine toIdempotentService(single source of truth)- Operation exceptions are rethrown as the original domain exception (no longer wrapped in
IdempotentException); the in-progress entry is still cleaned up first - Successful
null/voidresults are now cached asCOMPLETED(duplicate calls short-circuit instead of re-executing) - Non-2xx
ResponseEntityresults are not cached, so the caller can retry - New typed overloads:
IdempotentService.execute(key, returnType, operation, ttl)— lets RDS/DynamoDB deserialize into the target type without relying on Jackson polymorphic@classmetadata @Idempotent(duration = ...)accepts both ISO-8601 (PT5M) and Spring short forms (5m,30s,100ms)@Idempotentlogs a warning (once per method) when the resolved key is empty, instead of silently skipping idempotency
Breaking Changes by Area
Core / @Idempotent
- Removed
ttlInSeconds— usedurationonly IdempotentAspectis only registered when Spring AOP is on the classpath (addspring-boot-starter-aop); without it@Idempotentwon’t intercept.IdempotentServiceis always available.IdempotentAspectconstructor changed:(idempotentStore, properties)→(idempotentService, properties)IdempotentPayloadCodecExceptionnow extendsIdempotentExceptionIdempotentStore.Value.statususes enumIN_PROGRESS/COMPLETEDIdempotentStore.Value.expiresAtis nowInstant- Retry property renamed:
idempotent.inprogress.retry.initial.interval-millis→idempotent.inprogress.retry.initial.interval(Duration, e.g.100ms/PT0.1S) - Include only one backend module on the classpath, or provide your own
IdempotentStorebean
Redis
- See “Biggest Change” above
DynamoDB
- Removed
idempotent.dynamodb.use-local— useidempotent.dynamodb.endpointfor local/test - New
idempotent.dynamodb.enabled(defaulttrue) - New
idempotent.dynamodb.ttl-enabled(defaulttrue; setfalseif TTL is already configured on your table) - TTL attribute is
expiresAtEpochSeconds(epoch seconds); removed legacyexpirationTimeInMilliSeconds idempotent.aws.region/access-key/access-secretare now optional (omit to use the default AWS credential chain)idempotent.dynamodb.endpointis optional (null when unset)
NATS
- Property rename:
idempotent.nats.enable→idempotent.nats.enabled - Exception rename:
NatsIdempotentExceptions→NatsIdempotentException
RDS
- Schema column rename:
expiration_time_millis→expires_at(BIGINT, epoch milliseconds) — migrate/recreate your table - New
idempotent.rds.enabled(defaulttrue) - New
idempotent.rds.cleanup.enabled(defaulttrue) idempotent.rds.cleanup.fixed-delayis now aDuration(defaultPT1M)
Serialization
- Removed deprecated module-specific Jackson customizers:
IdempotentJacksonJsonBuilderCustomizer(redis/nats)RdsJacksonJsonBuilderCustomizer(rds)- Use
IdempotentJsonMapperCustomizerfromidempotent-core
Upgrade Checklist
- Add
spring-boot-starter-aopif you use@Idempotent. - Redis: move connection settings to
spring.data.redis.*; remove oldidempotent.redis.*connection properties (keepidempotent.redis.enabled). - DynamoDB: drop
use-local; reviewenabled/ttl-enabled/ endpoint; ensure your TTL attribute isexpiresAtEpochSeconds. - RDS: rename the schema column to
expires_at; review the newenabled/cleanup.*flags. - NATS: update the renamed property and exception references.
- Replace
ttlInSecondswithduration; update the retry interval property to aDuration. - If you have a custom store, implement
loadValue(...)and rely on the defaultgetValue(...)for expiry. - Re-run integration tests against your backend (Redis / DynamoDB / NATS / RDS).
What's Changed
🔨 Dependency Upgrades
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.42.32 to 2.42.34 by @dependabot[bot] in #101
- Bump com.uber.nullaway:nullaway from 0.13.2 to 0.13.3 by @dependabot[bot] in #100
- Bump softprops/action-gh-release from 2 to 3 by @dependabot[bot] in #98
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.42.34 to 2.42.35 by @dependabot[bot] in #102
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.42.35 to 2.42.40 by @dependabot[bot] in #108
- Bump spring-boot.version from 4.0.5 to 4.0.6 by @dependabot[bot] in #107
- Bump com.uber.nullaway:nullaway from 0.13.3 to 0.13.4 by @dependabot[bot] in #104
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.42.40 to 2.43.0 by @dependabot[bot] in #109
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.43.0 to 2.43.2 by @dependabot[bot] in #112
- Bump org.jreleaser:jreleaser-maven-plugin from 1.23.0 to 1.24.0 by @dependabot[bot] in #111
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.43.2 to 2.44.1 by @dependabot[bot] in #114
- Bump JetBrains/qodana-action from 2025.3 to 2026.1 by @dependabot[bot] in #113
- Bump io.nats:jnats from 2.25.2 to 2.25.3 by @dependabot[bot] in #117
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.44.1 to 2.44.3 by @dependabot[bot] in #116
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.44.3 to 2.44.4 by @dependabot[bot] in #118
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.44.4 to 2.44.6 by @dependabot[bot] in #121
- Bump com.diffplug.spotless:spotless-maven-plugin from 3.4.0 to 3.5.0 by @dependabot[bot] in #120
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.44.6 to 2.44.8 by @dependabot[bot] in #123
- Bump com.diffplug.spotless:spotless-maven-plugin from 3.5.0 to 3.5.1 by @dependabot[bot] in #122
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.44.8 to 2.44.9 by @dependabot[bot] in #124
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.44.9 to 2.44.10 by @dependabot[bot] in #126
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.44.10 to 2.44.13 by @dependabot[bot] in #130
- Bump software.amazon.awssdk:dynamodb-enhanced from 2.44.13 to 2.45.1 by @dependabot[bot] in #133
- Bump com.diffplug.spotless:spotless-maven-plugin from 3.5.1 to 3.6.0 by @dependabot[bot] in #131
Other Changes
- Prepare for next release by @github-actions[bot] in #97
- 3.0: Redis decoupling and store auto-config cleanup by @arun0009 in #127
Full Changelog: v2.4.1...v3.0.0