KState
KState is a lightweight, zero-dependency Kotlin library designed for mission-critical state management in financial and enterprise systems. Built for zero-error-tolerance environments with comprehensive testing, thread safety, and immutable state patterns.
Core Features
KState provides four main capabilities for building robust, production-grade applications:
๐ฏ 1. Immutable State Containers
Simple, thread-safe containers for managing immutable state with validation and atomic updates.
stateContainer()- Basic immutable state managementobservableStateContainer()- State containers with observer patternthreadSafeStateContainer()- Explicit thread-safe operations
๐ 2. Event-Driven State Machines
Declarative DSL for complex state machines with guarded transitions, lifecycle hooks, and side effects.
stateMachine()- Full-featured state machine builderGuards and validation for controlled transitions
Entry/exit actions and side effects
Multiple event handlers with short-circuiting
๐ 3. Saga Orchestration (Distributed Transactions)
Built-in saga pattern for managing multi-step workflows with automatic compensation.
sagaExecutor()- Saga orchestration with LIFO compensationAutomatic rollback on failure
Monitoring and observability hooks
Integration with state machines via
EventHandler.saga()
๐ 4. Observer Pattern & Validation
Rich notification system and validation framework for reactive programming.
StateObserver- State change observers with old/new state accessStateValidator- Custom validation rules enforced before transitionsAudit trails and telemetry integration
Why Choose KState?
Zero Dependencies, Maximum Reliability
Only requires Kotlin standard library - no external framework dependencies
Perfect for domain layer architecture and microservices
Minimal runtime footprint with maximum performance
Financial-Grade Safety
Thread-safe by design with atomic operations
Immutable state prevents accidental corruption
Comprehensive validation at every state transition
270 test cases covering edge cases, concurrency, saga compensation, and security
Developer Experience
Intuitive DSL for state machines and saga orchestration
Type-safe state transitions with compile-time guarantees
Rich observer pattern for reactive programming
Built-in saga pattern for distributed transactions with automatic compensation
Quick Start Examples
Requirements
JDK 17 or newer for builds and local development
Kotlin 2.1.0 or newer in consuming projects (matches the library target)
Gradle 8.x or Maven 3.8+
Installation
Gradle (Kotlin DSL):
repositories {
mavenCentral()
}
dependencies {
implementation("ca.acendas:kstate:1.6.1")
}Gradle (Groovy DSL):
repositories {
mavenCentral()
}
dependencies {
implementation 'ca.acendas:kstate:1.6.1'
}Maven:
<dependency>
<groupId>ca.acendas</groupId>
<artifactId>kstate</artifactId>
<version>1.6.1</version>
</dependency>Simple State Container
data class CounterState(val count: Int = 0)
val container = stateContainer(CounterState())
val result = container.updateState { state ->
state.copy(count = state.count + 1)
}Saga Orchestration (Distributed Transactions)
Execute multi-step workflows with automatic compensation on failure. Perfect for microservices, payment processing, and distributed transactions.
data class OrderContext(val items: List<String>, val amount: Double)
data class OrderResult(val orderId: String, val confirmed: Boolean)
val orderSaga = sagaExecutor<OrderContext, OrderResult> {
// Step 1: Reserve inventory
step("reserve-inventory") { context ->
val reservationId = inventoryService.reserve(context.items)
ReservationResult(reservationId)
}
compensate { result ->
inventoryService.release(result.reservationId)
}
// Step 2: Charge payment
step("charge-payment") { context ->
val transactionId = paymentService.charge(context.amount)
PaymentResult(transactionId)
}
compensate { result ->
paymentService.refund(result.transactionId)
}
// Step 3: Confirm order
step("confirm-order") { context ->
OrderResult("order-${System.currentTimeMillis()}", true)
}
// Monitor saga events
monitor { event ->
logger.info("Saga event: $event")
}
}
// Execute the saga
val result = orderSaga.execute(OrderContext(listOf("item1", "item2"), 99.99))
when (result) {
is SagaResult.Completed -> println("Order confirmed: ${result.value.orderId}")
is SagaResult.Aborted -> println("Order cancelled, compensated successfully")
is SagaResult.CompensationFailure -> println("Critical failure - manual intervention required")
}Key Saga Features:
Automatic LIFO (Last-In-First-Out) compensation on failure
Thread-safe execution with monitoring hooks
Type-safe step definitions and result handling
Integration with state machines for complex workflows
State Machine DSL
The stateMachine builder is the primary entry point for declaratively modelling event-driven workflows. It supports guarded transitions, lifecycle hooks, side effects, observer notifications, and remains coroutine-friendly and thread-safe.
Basic Usage
sealed interface PaymentState {
data class Pending(val amount: BigDecimal) : PaymentState
data class Authorized(val amount: BigDecimal, val authCode: String) : PaymentState
data class Captured(val amount: BigDecimal, val transactionId: String) : PaymentState
}
sealed interface PaymentEvent : StateEvent {
data class Authorize(val authCode: String) : PaymentEvent
data class Capture(val transactionId: String) : PaymentEvent
}
val paymentProcessor = stateMachine<PaymentState, PaymentEvent> {
initialState = PaymentState.Pending(BigDecimal("99.99"))
state<PaymentState.Pending> {
on<PaymentEvent.Authorize> { event ->
transitionWith { state, _ ->
PaymentState.Authorized(state.amount, event.authCode)
}
}
}
state<PaymentState.Authorized> {
on<PaymentEvent.Capture> { event ->
transitionWith { state, _ ->
PaymentState.Captured(state.amount, event.transactionId)
}
}
}
}
paymentProcessor.start()
paymentProcessor.send(PaymentEvent.Authorize("AUTH-123"))
paymentProcessor.send(PaymentEvent.Capture("TXN-456"))Coroutine Integration
import kotlinx.coroutines.*
sealed interface FileProcessorState {
data object Idle : FileProcessorState
data class Processing(val fileName: String, val progress: Int = 0) : FileProcessorState
data class Completed(val fileName: String, val result: String) : FileProcessorState
data class Failed(val fileName: String, val error: String) : FileProcessorState
}
sealed interface FileProcessorEvent : StateEvent {
data class StartProcessing(val fileName: String) : FileProcessorEvent
data class ProgressUpdate(val progress: Int) : FileProcessorEvent
data class ProcessingComplete(val result: String) : FileProcessorEvent
data class ProcessingFailed(val error: String) : FileProcessorEvent
}
val processorScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
// Domain-specific service responsible for the actual IO work
val fileService: FileService = obtainFileService()
val machine = stateMachine<FileProcessorState, FileProcessorEvent> {
initialState = FileProcessorState.Idle
state<FileProcessorState.Idle> {
on<FileProcessorEvent.StartProcessing> { event ->
sideEffect {
processorScope.launch {
fileService.process(event.fileName)
}
}
transitionTo(FileProcessorState.Processing(event.fileName))
}
}
state<FileProcessorState.Processing> {
on<FileProcessorEvent.ProgressUpdate> { event ->
transitionWith { state, _ -> state.copy(progress = event.progress) }
}
on<FileProcessorEvent.ProcessingComplete> { event ->
transitionTo(FileProcessorState.Completed(state.fileName, event.result))
}
on<FileProcessorEvent.ProcessingFailed> { event ->
transitionTo(FileProcessorState.Failed(state.fileName, event.error))
}
}
}This DSL keeps transitions explicit, enables domain-specific validation through guards, and plays well with structured concurrency when orchestrating asynchronous workflows. Use your own domain binding to supply obtainFileService() in the above snippet.
Observable State Container
val container = observableStateContainer(CounterState())
container.observe { oldState, newState ->
println("Count changed: ${oldState.count} -> ${newState.count}")
}
container.updateState { it.copy(count = it.count + 1) }State Validation
Enforce business rules before accepting state changes:
data class AccountState(val balance: Double, val overdraftLimit: Double)
val validator = StateValidator<AccountState> { state ->
when {
state.balance < -state.overdraftLimit ->
ValidationResult.Invalid(listOf("Balance exceeds overdraft limit"))
state.overdraftLimit < 0 ->
ValidationResult.Invalid(listOf("Overdraft limit cannot be negative"))
else -> ValidationResult.Valid
}
}
val account = stateContainer(
initialState = AccountState(balance = 1000.0, overdraftLimit = 500.0),
validator = validator
)
// This update will be rejected by the validator
val result = account.updateState {
it.copy(balance = -600.0) // Exceeds overdraft limit
}
when (result) {
is StateUpdateResult.Success -> println("Update successful")
is StateUpdateResult.ValidationFailure ->
println("Validation failed: ${result.errors}")
}Guards and Conditional Transitions
Control state transitions with guard conditions:
sealed interface OrderState {
data class Draft(val items: List<String>) : OrderState
data class Submitted(val items: List<String>, val total: Double) : OrderState
data object Cancelled : OrderState
}
sealed interface OrderEvent : StateEvent {
data object Submit : OrderEvent
data object Cancel : OrderEvent
}
val orderMachine = stateMachine<OrderState, OrderEvent> {
initialState = OrderState.Draft(emptyList())
state<OrderState.Draft> {
on<OrderEvent.Submit> {
// Guard: only allow submission if cart has items
guard { state -> state.items.isNotEmpty() }
transitionWith { state, _ ->
val total = state.items.size * 10.0 // Simple calculation
OrderState.Submitted(state.items, total)
}
}
on<OrderEvent.Cancel> {
transitionTo(OrderState.Cancelled)
}
}
}Event-Driven State Container
Simpler event handling without full state machine complexity:
sealed interface CounterEvent : StateEvent {
data object Increment : CounterEvent
data object Decrement : CounterEvent
data class Set(val value: Int) : CounterEvent
}
val counter = eventDrivenStateContainer<CounterState, CounterEvent>(
initialState = CounterState(0)
) { currentState, event ->
when (event) {
is CounterEvent.Increment -> currentState.copy(count = currentState.count + 1)
is CounterEvent.Decrement -> currentState.copy(count = currentState.count - 1)
is CounterEvent.Set -> currentState.copy(count = event.value)
}
}
counter.start()
counter.send(CounterEvent.Increment)
counter.send(CounterEvent.Set(42))Thread-Safe Concurrent Operations
Explicit thread-safe state management for high-concurrency scenarios:
data class InventoryState(val stock: Map<String, Int>)
val inventory = threadSafeStateContainer(
InventoryState(mapOf("item1" to 100, "item2" to 50))
)
// Safe from multiple threads
runBlocking {
launch {
inventory.updateState { state ->
val current = state.stock["item1"] ?: 0
state.copy(stock = state.stock + ("item1" to current - 1))
}
}
launch {
inventory.updateState { state ->
val current = state.stock["item2"] ?: 0
state.copy(stock = state.stock + ("item2" to current - 1))
}
}
}Saga with State Machine Integration
Combine sagas with state machines for complex workflows:
sealed interface CheckoutState {
data object Idle : CheckoutState
data class Processing(val orderId: String) : CheckoutState
data class Completed(val orderId: String) : CheckoutState
data class Failed(val error: String) : CheckoutState
}
sealed interface CheckoutEvent : StateEvent {
data class StartCheckout(val items: List<String>, val amount: Double) : CheckoutEvent
}
val checkoutMachine = stateMachine<CheckoutState, CheckoutEvent> {
initialState = CheckoutState.Idle
state<CheckoutState.Idle> {
on<CheckoutEvent.StartCheckout> {
saga<CheckoutState, CheckoutEvent, OrderContext, OrderResult> {
context { state, event ->
OrderContext(event.items, event.amount)
}
step("reserve-inventory") { context ->
inventoryService.reserve(context.items)
}
compensate { result ->
inventoryService.release(result.reservationId)
}
step("process-payment") { context ->
paymentService.charge(context.amount)
}
compensate { result ->
paymentService.refund(result.transactionId)
}
onComplete { result ->
CheckoutState.Completed(result.orderId)
}
onFailure { error ->
CheckoutState.Failed(error.exception.message ?: "Unknown error")
}
}
}
}
}Next steps
Explore the DSL reference for
stateMachine,stateContainer, and observer utilities in the side navigation.Combine these APIs with your validation logic to enforce domain rules before state transitions are committed.
Run
./gradlew test(ormvn test) to confirm the integration is wired correctly in your environment.
Main API Entry Points
State Management
stateContainer()- Create simple immutable state containers with validationobservableStateContainer()- State containers with observer notificationsthreadSafeStateContainer()- Explicit thread-safe state operationseventDrivenStateContainer()- Event-based containers for simpler workflows
State Machines & Workflows
stateMachine()- Build complex event-driven state machines with DSLsagaExecutor()- Orchestrate multi-step sagas with automatic compensationEventHandler.saga()- Integrate sagas within state machine event handlers
Core Types & Utilities
StateValidator- Define custom validation rules for state transitionsStateObserver- React to state changes with observer patternSagaMonitor- Monitor saga execution with lifecycle eventsSagaResult- Result types for saga execution (Completed, Aborted, CompensationFailure)StateEvent- Base interface for all state machine events
Architecture & Concepts
Immutable state shapes, atomic operations, and thread-safe containers keep state transitions deterministic.
Validation pipelines (
StateValidator,ValidationResult) enforce domain rules before accepting new state snapshots.Event-driven builders scope handlers, side effects, and lifecycle hooks (
onEnter,guard,sideEffect) to specific states.Namespace layout mirrors the architecture:
corefor containers,eventsfor the state machine DSL,observersfor change notifications,validationfor business rule enforcement, andutilsfor supporting extensions.
Production Readiness
High Performance: >10,000 operations/second, <1ms latency
Comprehensive Testing: 270 test cases covering state machines, sagas, concurrency, and edge cases
Financial Grade: Suitable for zero-error-tolerance environments with saga compensation guarantees
Thread Safety: All operations are safe for concurrent access
Zero Dependencies: Perfect for domain layer architecture and microservices
Observability: Observer hooks, saga monitors, audit trails, and side effects integrate with logging/telemetry
Validation: Custom validators enforce business rules before state transitions commit
Saga Support: LIFO compensation, monitoring, and failure recovery for distributed transactions
Clean Architecture Integration
Domain-Centric Design: Because KState ships as pure Kotlin with zero runtime dependencies, it satisfies Uncle Bob's dependency ruleโyour domain layer depends on nothing but your own models and the Kotlin standard library.
Use Case Coordination: Wrap important domain workflows (aggregates, policies, sagas) in
stateMachinebuilders to express use case orchestration without bleeding infrastructure concerns into the domain.Interactor-Friendly:
stateContainerandobservableStateContainercompose neatly with application services or interactors, allowing immutable state updates that can be unit-tested in isolation.Boundary Isolation: Emit domain events from state transitions, then let outer layers translate them into messaging, persistence, or UI updates. Nothing in KState requires Android, Spring, or other frameworks.
Testing in Isolation: The comprehensive test APIs and absence of platform dependencies mean you can run domain-layer tests on the JVM or in multiplatform setups with no mock-heavy infrastructure.
Operational runbooks (release management, internal tooling, contributor process) are available to partners through private channels and are intentionally omitted from this public package reference.
For comprehensive type-level details, browse the API Reference via the left-hand navigation.