Kotlin
Sagaweaw works natively with Kotlin. The sagaweaw-kotlin module provides an idiomatic DSL that eliminates all Java interop friction — no Consumer<T> wrappers, no ::class.java, no Optional.
Kotlin version
sagaweaw-kotlin works with Kotlin 2.0+. No version upgrade needed for Spring Boot 3.x projects.
Add the dependency
Add sagaweaw-kotlin alongside the main starter:
Maven
<dependency>
<groupId>dev.sagaweaw</groupId>
<artifactId>sagaweaw-spring-boot-starter</artifactId>
<version>1.0.12</version>
</dependency>
<dependency>
<groupId>dev.sagaweaw</groupId>
<artifactId>sagaweaw-kotlin</artifactId>
<version>1.0.12</version>
</dependency>
Gradle
implementation("dev.sagaweaw:sagaweaw-spring-boot-starter:1.0.12")
implementation("dev.sagaweaw:sagaweaw-kotlin:1.0.12")
What changes with Kotlin
1. Context — KSagaContext instead of SagaContext
The Java SagaContext interface returns Optional<String> from businessKey(). In Kotlin, use KSagaContext and override key() with a plain String?:
// Without sagaweaw-kotlin
data class OrderContext(val orderId: UUID) : SagaContext {
override fun businessKey() = Optional.of(orderId.toString()) // ❌ Java Optional
}
// With sagaweaw-kotlin
data class OrderContext(val orderId: UUID) : KSagaContext() {
override fun key() = orderId.toString() // ✅ plain String
}
2. Saga definition — clean DSL
The Java builder has two invoke overloads that cause SAM conversion ambiguity in Kotlin. The sagaweaw-kotlin DSL resolves this with a block-based syntax:
// Without sagaweaw-kotlin — SAM ambiguity forces explicit Consumer<T>
saga.step("charge-payment")
.invoke(Consumer { ctx -> paymentService.charge(ctx.amount) })
.compensate(Consumer { ctx -> paymentService.refund(ctx.orderId) })
// With sagaweaw-kotlin — idiomatic lambda, no type annotations needed
saga.step("charge-payment") {
invoke { ctx -> paymentService.charge(ctx.amount) }
compensate { ctx -> paymentService.refund(ctx.orderId) }
}
3. Retry — kotlin.time.Duration
// Without sagaweaw-kotlin
.retryPolicy(RetryPolicy.exponential(3, Duration.ofSeconds(5)))
// With sagaweaw-kotlin
retry(exponentialRetry(3, 5.seconds))
4. Start — reified extension
// Without sagaweaw-kotlin
sagaManager.start(OrderSaga::class.java, context)
// With sagaweaw-kotlin
sagaManager.start<OrderSaga>(context)
Full example
import io.sagaweaw.kotlin.*
import kotlin.time.Duration.Companion.seconds
data class OrderContext(
val orderId: UUID,
val customerId: UUID,
val amount: BigDecimal,
var chargeId: String? = null,
) : KSagaContext() {
override fun key() = orderId.toString()
}
@Saga(name = "order-processing")
@Component
class OrderSaga(
private val inventoryService: InventoryService,
private val paymentService: PaymentService,
private val shippingService: ShippingService,
) : SagaDefinition<OrderContext> {
override fun define(saga: SagaBuilder<OrderContext>) = saga
.step("reserve-inventory") {
invoke { ctx -> inventoryService.reserve(ctx.orderId) }
compensate { ctx -> inventoryService.release(ctx.orderId) }
}
.step("charge-payment") {
invoke { ctx ->
ctx.chargeId = paymentService.charge(ctx.customerId, ctx.amount)
}
compensate { ctx ->
ctx.chargeId?.let { paymentService.refund(it) }
}
retry(exponentialRetry(3, 5.seconds))
}
.step("create-shipment") {
invoke { ctx -> shippingService.schedule(ctx.orderId) }
}
.build()
}
Firing the saga:
@Service
class OrderService(
private val sagaManager: SagaManager,
) {
fun createOrder(request: CreateOrderRequest): UUID {
val context = OrderContext(
orderId = UUID.randomUUID(),
customerId = request.customerId,
amount = request.amount,
)
val execution = sagaManager.start<OrderSaga>(context)
return execution.sagaId()
}
}
What's included in sagaweaw-kotlin
| Feature | What it does |
|---|---|
KSagaContext | Base class with String? instead of Optional<String> for businessKey |
step("name") { } DSL | Block-based step definition — no Consumer<T>, no type annotations |
invoke { ctx -> } | Unambiguous lambda inside the step DSL |
compensate { ctx -> } | Unambiguous lambda inside the step DSL |
retry(policy) | Shorthand for retryPolicy() inside the step DSL |
timeout(duration) | kotlin.time.Duration support for step timeout |
exponentialRetry(n, duration) | kotlin.time.Duration factory for exponential retry |
fixedRetry(n, duration) | kotlin.time.Duration factory for fixed retry |
infiniteRetry(duration) | kotlin.time.Duration factory for infinite retry |
sagaManager.start<MySaga>(ctx) | Reified extension — no ::class.java needed |