Kotlin / JVM 24

Type-Safe
State Machine
for Kotlin

A declarative DSL for defining state transitions with compile-time type checking, composable guards, chainable actions, and full thread-safe immutability.

DocumentStateMachine.kt
click a state to transition
Publish Archive Archive DRAFT initial state PUBLISHED content live ARCHIVED read-only
> Current state: DRAFT

Built for safety,
designed for elegance

Every design decision prioritizes compile-time correctness and developer ergonomics.

Type-Safe DSL

Compile-time type checking for states, events, and context. Invalid transitions are caught before runtime.

Guard Conditions

Composable boolean predicates with and, or, not operators for expressive transition rules.

Chainable Actions

Side effects and context transformations with the then operator for clean, sequential processing.

Stateful Interface

Automatic state management through Stateful<S, C> for seamless integration with your domain models.

Transition Callbacks

Hook into state changes for logging, event publishing, auditing, or any cross-cutting concerns.

Immutable Design

All transitions produce new context instances. No mutation, no race conditions, full thread safety guaranteed.

Expressive, readable,
and type-safe

Define complex state machines with a clean Kotlin DSL that reads like a specification.

DocumentStateMachine.kt
 1val machine = stateMachine<DocumentStatus, DocumentEvent, Document> {
 2    from(DocumentStatus.DRAFT) {
 3        on<DocumentEvent.Publish>() goto DocumentStatus.PUBLISHED
 4        on<DocumentEvent.Archive>() goto DocumentStatus.ARCHIVED
 5    }
 6    from(DocumentStatus.PUBLISHED) {
 7        on<DocumentEvent.Archive>() goto DocumentStatus.ARCHIVED
 8    }
 9}
10
11val doc = Document(DocumentStatus.DRAFT, "Hello World")
12val result = machine.fire(doc, DocumentEvent.Publish)
13// result.newState == DocumentStatus.PUBLISHED
GuardedTransitions.kt
 1val machine = stateMachine<DocumentStatus, DocumentEvent, Document> {
 2    from(DocumentStatus.DRAFT) {
 3        on<DocumentEvent.Publish>() goto DocumentStatus.PUBLISHED guardedBy {
 4            it.content.isNotBlank()
 5        } action { doc, _ ->
 6            doc.copy(publishedAt = Instant.now())
 7        }
 8    }
 9}
10
11// Guard prevents transition when content is blank
12val emptyDoc = Document(DocumentStatus.DRAFT, "")
13machine.canFire(emptyDoc, DocumentEvent.Publish) // false
ComposableGuards.kt
 1// Define reusable guard predicates
 2val notBlank: Guard<Document> = Guard { it.content.isNotBlank() }
 3val longEnough: Guard<Document> = Guard { it.content.length >= 10 }
 4
 5// Compose with boolean operators
 6val publishable = notBlank and longEnough
 7val archivable  = notBlank or Guard { it.state == DocumentStatus.PUBLISHED }
 8val notArchived = !Guard<Document> { it.state == DocumentStatus.ARCHIVED }
 9
10// Chainable actions with 'then'
11val setTimestamp: Action<Document, DocumentEvent> = Action { doc, _ ->
12    doc.copy(publishedAt = Instant.now())
13}
14val normalize: Action<Document, DocumentEvent> = Action { doc, _ ->
15    doc.copy(content = doc.content.trim().lowercase())
16}
17val publishAction = setTimestamp then normalize
TransitionCallbacks.kt
 1val machine = stateMachine<DocumentStatus, DocumentEvent, Document> {
 2    from(DocumentStatus.DRAFT) {
 3        on<DocumentEvent.Publish>() goto DocumentStatus.PUBLISHED
 4        on<DocumentEvent.Archive>() goto DocumentStatus.ARCHIVED
 5    }
 6    from(DocumentStatus.PUBLISHED) {
 7        on<DocumentEvent.Archive>() goto DocumentStatus.ARCHIVED
 8    }
 9
10    // Hook into every state transition
11    onTransition { from, event, to ->
12        logger.info("Transition: $from -> $to via $event")
13        eventBus.publish(StateChanged(from, to))
14    }
15}

Add to your project
in seconds

Available via JitPack. Add the repository and dependency to your Gradle build file.

1

Add JitPack repository

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}
2

Add dependency

// build.gradle.kts
dependencies {
    implementation("com.github.Tianea2160:statemachine:v1.0.0")
}

Simple, powerful
core API

A minimal surface area that covers complex state management needs.

Component Type Description
State interface Marker interface for state types (typically enum classes)
Event interface Marker interface for event types (typically sealed interfaces)
Stateful<S, C> interface Context interface with state property and withState() method
Guard<C> fun interface Predicate (C) -> Boolean with and, or, not operators
Action<C, E> fun interface Function (C, E) -> C with chainable then operator
fire(model, event) method Execute a transition, returns TransitionResult<S, C>
canFire(model, event) method Check if a transition is valid without executing it
availableEvents(model) method Get all valid events for the current state and context