0.1.0
First public release of bluetape4k-leader. All APIs are stable unless noted as experimental.
Breaking Changes
-
runIfLeader()never throws on lock contention: returnsnullinstead of propagating
backend exceptions when the lock is not acquired — aligns with ShedLock skip-on-contention
semantics.CancellationExceptionandInterruptedExceptionare still rethrown. (PR #15)// Null means "not elected" or "action threw" — check logs val result = leaderElector.runIfLeader("job") { riskyWork() } // To preserve exception propagation, wrap in the action: leaderElector.runIfLeader("job") { try { riskyWork() } catch (e: MyException) { handleError(e); throw e } }
-
leader-exposed-jdbc:ExposedJdbcLeaderElector.runIfLeader()now returnsnullinstead
of rethrowing action exceptions.CancellationExceptionandInterruptedExceptionare still
rethrown. (issue #50) -
leader-exposed-r2dbc: Same change forExposedR2DbcSuspendLeaderElector.runIfLeader().
CancellationExceptionis still rethrown. -
leader-exposed-jdbc/leader-exposed-r2dbc: Elector factories now accept an optional
historyRecorderparameter (SafeLeaderHistoryRecorder?/SuspendSafeLeaderHistoryRecorder?).
The previousrecordHistoryoption in election options is superseded. -
LeaderElection/LeaderGroupElectionrenamed toLeaderElector/LeaderGroupElector
for consistency across all interfaces. (PR #106, #123, #125) -
Duration APIs: migrated from
java.time.Durationtokotlin.time.Duration. (PR #126) -
LeaderElectionEvent.Electednow carries optionalleaderId: String?and
leaseExpiry: Instant?fields (both default tonull). Compiled callers that construct
Elected(lockName)positionally will fail to link against new bytecode; recompilation is
required (source-compatible). -
Spring Boot 3/4 split consolidated:
leader-spring-boot-common,leader-spring-boot3, and
leader-spring-boot4merged into a singleleader-spring-bootmodule. (PR #105)
Added
leader-core — Core interfaces and local in-process implementations:
LeaderElector— blocking single-leader interfaceAsyncLeaderElector—CompletableFuture-based async interfaceVirtualThreadLeaderElector— virtual-thread-per-election interfaceSuspendLeaderElector— Kotlin coroutine suspend interfaceLeaderGroupElector— blocking multi-leader (semaphore) interfaceSuspendLeaderGroupElector— coroutine multi-leader interfaceLeaderElectionOptions(waitTime, leaseTime)— shared option data classLeaderGroupElectionOptions(maxLeaders, waitTime, leaseTime)— group option data class- Local implementations:
LocalLeaderElector,LocalLeaderGroupElector,
LocalSuspendLeaderElector,LocalSuspendLeaderGroupElector,LocalAsyncLeaderElector,
LocalVirtualThreadLeaderElector LockAssert—assertLocked()/assertLocked(lockName)/isLocked()and suspend variantsLockExtender—extendActiveLock(Duration): Boolean+ detailed sealedExtendOutcomeresult- suspend variants
LeaderLockHandlesealed class (Real/FailOpen) — explicit lease handleLeaderLeaseAutoExtender— periodic background lease renewal withshutdown()/restart()ListeningLeaderElector/ListeningLeaderGroupElector— listener-aware decorators with a hot
events: Flow<LeaderElectionEvent>stream (issue #40, PR #146)TenantScopedLeaderElectors—forTenant(tenantId)extension functions for multi-tenant
lock-name scoping (issue #42)- Strategic election API (issue #29, #31, #32):
CandidateInfo,ElectionStrategy,CandidateScorerinterfaces- Built-in strategies:
FifoElectionStrategy,RandomElectionStrategy,ScoredElectionStrategy - Built-in scorers:
IdleTimeScorer,SuccessRateScorer,RecentSuccessScorer,WeightedScorer - Redis
CandidateRegistry(Redisson sorted-set/hash + TTL; Lettuce variant included)
LeaderSlotaudit identity propagation:LeaderSlot(lockName, leaderId)propagates to
LeaderRunResult.Elected.leaderId(issue #72)
leader-redis-lettuce — Lettuce Redis backend:
LettuceLeaderElector— blocking, usesSET NX PXviaLettuceLockLettuceLeaderGroupElector— blocking multi-leader viaLettuceSlotTokenGroup(ZSET + Lua TTL)LettuceSuspendLeaderElector— coroutine single-leaderLettuceSuspendLeaderGroupElector— coroutine multi-leaderLettuceLock,LettuceSuspendLock— Redis distributed lock primitives (self-contained, nobluetape4k-lettucedependency)
leader-redis-redisson — Redisson Redis backend:
RedissonLeaderElector— blocking viaRLock.tryLock()RedissonLeaderGroupElector— blocking multi-leader viaRPermitExpirableSemaphoreRedissonSuspendLeaderElector— coroutine single-leader with PID-seeded Snowflake lock IDRedissonSuspendLeaderGroupElector— coroutine multi-leader
leader-exposed-core: Shared Exposed table DDL (LeaderLockTable, LeaderGroupLockTable) for
JDBC and R2DBC backends. (issue #23)
leader-exposed-jdbc — Exposed JDBC blocking backend (issue #21, PR #52):
ExposedJdbcLeaderElector— blocking single-leader (sync +CompletableFutureasync)ExposedJdbcLeaderGroupElector— blocking multi-leader- H2/PostgreSQL/MySQL Testcontainers integration tests
leader-exposed-r2dbc — Exposed R2DBC coroutine backend (issue #22, PR #62):
ExposedR2DbcSuspendLeaderElector— coroutine single-leaderExposedR2DbcSuspendLeaderGroupElector— coroutine multi-leader- R2DBC PostgreSQL Testcontainers integration tests
leader-mongodb — MongoDB backend (issue #8, PR #46):
MongoLeaderElector— blocking,findOneAndUpdateupsert +deleteOne(token)owner-only releaseMongoSuspendLeaderElector— coroutine, Kotlin coroutine driverMongoLeaderGroupElector— blocking multi-leader (lockName:slot:Nslot model)MongoSuspendLeaderGroupElector— coroutine multi-leader (dual-collection design)- TTL index auto-creation at startup
- 82.4% line coverage (42 tests, Testcontainers MongoDB)
leader-hazelcast — Hazelcast IMap token-lock backend (issue #9):
HazelcastLeaderElector,HazelcastLeaderGroupElector— blocking single/multi-leaderHazelcastSuspendLeaderElector,HazelcastSuspendLeaderGroupElector— coroutine variants
leader-zookeeper — ZooKeeper/Curator backend. (PR #138)
leader-micrometer — Micrometer metrics integration:
MicrometerLeaderElectionListenerrecordsleader.election.eventswithlock.nameandeventtags (issue #40, PR #146)
leader-spring-boot — Spring Boot 4 auto-configuration + AOP:
@LeaderElection/@LeaderGroupElectionannotations with AspectJ compile-time weaving (CTW)- Supports
suspend,Mono,Flux, and KotlinFlowreturn types (#74, #90, #91)@LeaderElection(streamBounded = true)explicit opt-in for bounded streams
LeaderAnnotationValidatorBeanPostProcessor— startup validation; blocks unsafe return types
(CompletableFuture,Deferred, etc.) (PR #79)LockAssert/LockExtenderintegration in AOP aspect (issue #79)LeaderMetricsHealthIndicator— Spring Boot Actuator health indicator (registered asleaderMetricsHealthIndicator)LeaderLeaseAutoExtenderLifecycle— context-lifecycle-aware auto-extender integration- Backend auto-configurations: Lettuce, Redisson, Exposed JDBC, Exposed R2DBC, MongoDB, Hazelcast
LeaderProperties—bluetape4k.leader.*configuration properties
leader-ktor — Ktor 3.x integration (issue #37, PR #164):
LeaderElectionPlugin—createApplicationPluginDSL,SuspendLeaderElector-basedApplication.leaderScheduled(lockName, period) { }— Spring@Scheduled-style leader-only
periodic task helper; auto-cancelled onApplicationStopped
leader-bom — Bill of Materials for consumers. All leader-* modules included; BOM users
do not need to specify individual versions.
examples/ — Runnable example applications (issue #36):
batch-scheduler— Lettuce Redis periodic batch single-execution (PR #159)migration-gate— Exposed JDBC boot-time schema migration gate (PR #160)webhook-poller— MongoDB single-instance webhook polling (PR #161)cache-warmer— Hazelcast per-partition leader cache warming (PR #162)tenant-aggregator— Exposed R2DBC coroutine multi-tenant aggregation (PR #163)ktor-app— Ktor 3.x + Lettuce RedisleaderScheduled()demo (PR #166)prometheus-dashboard— Spring Boot + Prometheus/Grafana leader metrics dashboard
CI/CD (issue #13, #35, PR #19, #20, #44, #135):
- GitHub Actions build, test, secret-scan, Gradle wrapper validation
- Nightly SNAPSHOT auto-publish (on test success only)
- Parallel test jobs for Lettuce and Redisson backends
Changed
LeaderElectionAspect/LeaderGroupElectionAspectouter catch narrowed toException(not
Throwable) to letOutOfMemoryError/StackOverflowErrorpropagate.LeaderElectionOptions,LeaderGroupElectionOptions,LeaderGroupStatevalidate eagerly in
init {}(waitTime ≥ 0,leaseTime > 0,maxLeaders ≥ 1). (PR #25)ExposedJdbcGroupLock.tryLock()return type changed toBoolean?:null= DB error,
false= slot contention,true= acquired. (issue #60, PR #63)- Suspend tests use
runBlocking(Dispatchers.IO)instead ofrunTestfor real-IO
(MongoDB / Testcontainers) tests. - CI uses paths-filter and retry configuration to reduce unnecessary work and transient failures.
(PR #135) - Prometheus export coverage verified through
PrometheusServerscrape tests. (PR #144) leader-bomNMCP aggregation and Central Snapshots publishing fixed. (PR #140)- factory
create()I/O failures now respect the configured failure mode. (PR #107) @ConfigurationPropertiesadded to AOP properties binding where missing. (PR #93)
Fixed
- Coroutine cancellation safety:
unlock/releasein all coroutine backends (Lettuce,
Redisson, Hazelcast, MongoDB) wrapped inwithContext(NonCancellable)to prevent lock leaks
on cancellation. (PR #25, review 2026-05-01) CancellationExceptionrethrow:catch(CancellationException) { throw e }added before
allcatch(Exception)blocks, including insidewithContext(NonCancellable). (PR #45)- Lettuce observability:
runCatching { unlock }failures now logged via.onFailure { log.warn };
previously, token-mismatch / Redis errors were silently discarded. ExposedJdbcGroupLock.isHeldByCurrentInstance(): added missing token +lockedUntil > NOW()
check. (issue #59, PR #63)ExposedJdbcGroupLock.tryLock()DB error propagation:Boolean?tri-state separates DB
errors from slot contention. (issue #60, PR #63)- Exposed lock SELECT predicate:
lockedUntil > NOW()added to JDBCtryAcquireOnceStep 3
SELECT to prevent split-brain; symmetric with R2DBC. (review 2026-05-04) leader-redis-redissoncoroutine lock ID: replacedbluetape4k-idgeneratorscompileOnly
dependency (causedClassNotFoundExceptionat runtime) with a self-contained PID-seeded
mini-Snowflake generator. (issue #3, PR #17)leader-redis-lettuce: portedLettuceLockprimitives directly, removing the runtime
dependency onbluetape4k-lettuce. (PR #2)- Kover coverage aggregation: fixed missing-module aggregation bug in the CI coverage script.
- Deprecated API replaced:
TimebasedUuid.Epoch→Uuid.V7(Kotlin 2.3+).
Removed
The following deprecated APIs were removed before 0.1.0 GA (#264):
| Item | Replacement |
|---|---|
LeaderLease.leaderId property |
LeaderLease.auditLeaderId |
LeaderLeaseAutoExtender.start(Boolean lambda) overload |
start(ExtendDelegate) form |
HistoryStatus typealias (HistoryStatus.kt deleted) |
LeaderHistoryStatus |
RetryStrategy typealias (RetryStrategy.kt deleted) |
(zero callers — removed) |
ExposedJdbcGroupLock.extend() |
(no production callers — removed) |
ExposedJdbcLock.extend() |
(no production callers — removed) |
MongoLock.extend() |
(no callers — removed) |
MongoSuspendLock.extend() |
(no callers — removed) |
LettuceSemaphore class (@Deprecated, not removed) |
LettuceLeaderGroupElector (slot-token TTL model) |
LettuceSuspendSemaphore class (@Deprecated, not removed) |
LettuceSuspendLeaderGroupElector |
Full Changelog: https://github.com/bluetape4k/bluetape4k-leader/commits/0.1.0