Skip to main content

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

FeatureWhat it does
KSagaContextBase class with String? instead of Optional<String> for businessKey
step("name") { } DSLBlock-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