Examples

Self-contained workflows that show Skipper's features in context. Each focuses on a single idea; follow the linked guides for the full picture. Toggle Kotlin or Java on any snippet.

Peer-to-peer transfer

A money transfer that requires manual approval over a threshold, then debits and credits a ledger. The workflow hibernates while it waits for the approval signal.

class PeerToPeerTransfer : Workflow() {
  private val ledger = actions<LedgerActions>()

  @StateField var isApproved: Boolean? = null

  @WorkflowMethod
  suspend fun transfer(request: TransferRequest): Boolean {
    if (request.amount > 1000) {
      // Hibernate until approved — or up to a day — using zero resources.
      val approved = waitUntil({ isApproved != null }, Duration.ofDays(1))
      if (!approved || isApproved != true) return false
    }
    ledger.debit(request.from, request.amount)
    ledger.credit(request.to, request.amount)
    return true
  }

  @SignalMethod
  fun approve(approved: Boolean) { this.isApproved = approved }
}
waitUntilsignalsstate

Order processing with compensation

Charge, reserve inventory, and ship. If any step fails, Skipper automatically runs the compensation methods for completed actions in reverse order — the saga pattern.

class OrderWorkflow : Workflow() {
  private val payments = actions<PaymentActions>()
  private val inventory = actions<InventoryActions>()
  private val shipping = actions<ShippingActions>()

  @WorkflowMethod
  suspend fun process(order: OrderRequest): OrderResult {
    payments.charge(order)      // compensated by refund if a later step fails
    inventory.reserve(order)    // compensated by release if a later step fails
    shipping.schedule(order)    // if this throws, the two above are undone
    return OrderResult.completed(order.id)
  }
}

class PaymentActions : Actions() {
  @Execute
  suspend fun charge(order: OrderRequest): String = gateway.charge(order)

  @Compensate(forExecute = "charge")
  suspend fun refund(order: OrderRequest, chargeId: String) = gateway.refund(chargeId)
}
compensationactions

Key topics: Compensation (Saga)

Approval workflow

A document review that pauses for up to a week waiting on a human decision delivered by a signal, and exposes its pending state through a query.

class DocumentApproval : Workflow() {
  @StateField var decision: Decision? = null

  @WorkflowMethod
  suspend fun review(doc: Document): Outcome {
    // Wait up to a week for a human decision; no resources held while waiting.
    val received = waitUntil({ decision != null }, Duration.ofDays(7))
    return when {
      !received                     -> Outcome.EXPIRED
      decision == Decision.APPROVED -> Outcome.APPROVED
      else                          -> Outcome.REJECTED
    }
  }

  @SignalMethod fun submit(decision: Decision) { this.decision = decision }

  @QueryMethod fun isPending(): Boolean = decision == null
}
signalsquerieslong waits

Key topics: Signals & Queries

Batch processing

Fan out over a list of items. Stable, per-item checkpoint names mean the loop resumes exactly where it left off after an interruption, without reprocessing finished items.

class BatchWorkflow : Workflow() {
  private val processor = actions<ItemActions>()

  @WorkflowMethod
  suspend fun run(batch: Batch): BatchResult {
    var processed = 0
    batch.items.forEach { item ->
      // A stable per-item checkpoint name keeps the loop safe across resumes.
      processor.named("item-${item.id}").process(item)
      processed++
    }
    return BatchResult(processed)
  }
}
named checkpointsloops