Open source · Apache 2.0

Durable execution, embedded in your service

Skipper is a lightweight workflow engine for the JVM. Write complex, long-running business logic as plain Java or Kotlin — and let Skipper guarantee it runs to completion, in spite of failures.

No cluster to operate. No new database. Just a dependency: dev.skipper:skipper — coordinates are illustrative until the first public release.

class CheckoutWorkflow : Workflow() {
  private val payments = actions<PaymentActions>()
  private val email = actions<EmailActions>()

  @WorkflowMethod
  suspend fun checkout(order: Order): Receipt {
    // Charged once — even if the workflow resumes later.
    val charge = payments.charge(order)
    // Retried automatically until it succeeds.
    email.sendReceipt(order, charge)
    return Receipt(charge.id)
  }
}
Why Skipper

Workflow durability without the operational tax

The reliability of a workflow engine, delivered as a library you already know how to run.

Durable by default

Workflows are guaranteed to reach a terminal state despite transient failures. Skipper classifies and retries errors for you, so your business logic stays clean.

A library, not a cluster

Embed Skipper in your existing service or run it as a sidecar. There is no separate cluster to deploy, scale, or get paged about at 3am.

Reuses your database

Start instantly on an embedded store, then point Skipper at the MySQL your service already runs — all behind pluggable adapters. No new critical dependency.

Show me the code

A workflow is just a class

This peer-to-peer transfer waits for manual approval on large amounts, then moves the money. Notice what is not here: retries, state persistence, and failure handling are the engine's job.

  • No error-handling boilerplate. Skipper retries transient failures; your method stays pure business logic.
  • waitUntil hibernates. The workflow can pause for a day or longer, consuming zero resources, then resume exactly where it left off.
  • Signals wake it up. approve(...) is called from the outside world — an API handler, a queue consumer, or a human.
  • Automatic checkpoints. Completed actions are persisted, so a resumed workflow never repeats work it has already done.
class PeerToPeerTransfer : Workflow() {
  private val ledger = actions<Ledger>()

  @StateField var isApproved: Boolean? = null

  @WorkflowMethod
  suspend fun transfer(request: TransferRequest): Boolean {
    // Transfers over $1,000 need a human in the loop.
    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
  }

  // A signal wakes the waiting workflow up from the outside world.
  @SignalMethod
  fun approve(isApproved: Boolean) {
    this.isApproved = isApproved
  }
}
Features

Everything a durable workflow needs

Batteries-included primitives for the hard parts of long-running, stateful processes.

Durable state & checkpoints

Completed action results are persisted and restored, so side effects never run twice when a workflow resumes.

Signals & queries

Push data into a running workflow with @SignalMethod, or read its state with @QueryMethod. Signals can be made durable.

Compensation (Saga)

Add @Compensate methods and Skipper unwinds completed actions in reverse order when a workflow fails.

Retries & error handling

A pluggable exception classifier, fixed / exponential / persistent retry strategies, and a dead-letter queue for the rest.

Versioning & evolution

Named checkpoints and version gates let you change running workflows without breaking in-flight instances.

First-class testing

SkipperTest helpers run workflows end-to-end in-memory and assert on state, completion, and compensation.

Admin UI & metrics

A built-in admin UI inspects running instances; core dashboards track actions, scheduler, and storage.

Pluggable storage

Storage and scheduler sit behind clean interfaces — bring your own backend or use the bundled MySQL adapter.

How it works

How durable execution works

Four moving parts. Your code touches the first two; Skipper handles the rest.

1. Workflow

Your deterministic business logic, written as a plain Java or Kotlin class.

2. Actions

The side-effecting steps — I/O, RPCs, database writes — that the workflow calls.

3. Checkpoints

Completed actions and state are persisted, so finished work is never repeated.

4. Hibernate & resume

Waits consume no resources; the workflow resumes automatically when a signal or timer fires.

Is it for you?

Is Skipper a good fit?

Skipper is opinionated. Here is where it shines — and where it does not.

A great fit if…

  • You need durable execution — the workflow must run to a terminal state in spite of failures.
  • Your process coordinates multiple steps, long waits, or human approvals.
  • You would consider Temporal, but you do not want to run and operate a cluster.
  • Your service is Tier 0 and cannot take a dependency on an external workflow cluster.

Maybe not the right tool if…

  • Your workflow does not require durable execution.
  • Your service is written in a non-JVM language.
Open source

Built in the open

Skipper is released under the Apache 2.0 license. Issues, discussions, and pull requests are welcome — see the community page to get involved.