Pause and resume

Pause and resume record processing at runtime with POST /pause and /unpause — for maintenance windows and downstream backpressure, without restarting the instance.

Pause stops the instance from dispatching new records and tracking new offsets, while keeping the consumer session alive and the lane threads warm — so you can resume instantly. It's a runtime control for short operational gaps (a maintenance window on a downstream system, transient backpressure), not a substitute for shutting the instance down.

What pause does

Calling pause moves the instance through two states:

  1. DRAINING — record dispatch stops immediately. No new records are handed to processing; timers and punctuators are paused; records already consumed but not yet dispatched are held, not dropped. Work already in flight finishes, and a final commit flushes that work and its offsets. This is a graceful drain — nothing in flight is dropped.
  2. PAUSED — reached automatically once the in-flight work has drained and committed. The instance now sits idle: the Kafka consumer keeps polling for heartbeats (so the group membership and session stay alive), processing threads stay warm and ready, but no new records are consumed and no new offsets are tracked.

Because the consumer keeps heartbeating, pausing does not trigger a consumer-group rebalance and does not require a restart. Resume is near-instant — the threads never went cold.

Pause is in-memory state, not persisted. If the process restarts while paused, it comes back up in the normal RUNNING flow and resumes processing — there is no "stays paused across restarts" behaviour.

What unpause does

Unpause returns the instance to RUNNING:

  • Record dispatch resumes — any held records drain back into processing and consumption continues.
  • Timers and punctuators resume.

You can call unpause from either PAUSED (resume after the drain finished) or DRAINING (cancel the drain that's still in progress and resume immediately).

Pause and readiness

This is the key operational interaction. While the instance is DRAINING or PAUSED:

ProbeStateMeaning
/health/liveUPThe process is healthy — do not restart it.
/health/readyDOWNThe instance is intentionally not taking traffic.

Liveness staying UP is deliberate: a paused instance is working as intended, so an orchestrator must not kill and restart it. Readiness going DOWN reflects that it isn't processing. See Health checks and Probes for how Kubernetes consumes these.

StoatFlow runs as a single active instance — there is no second replica to pick up traffic while this one is paused. Pausing halts the pipeline; consumer lag will grow on the source topics for as long as you stay paused. Pause is for short, deliberate gaps, not load shedding. (Even with hot standby enabled, pausing the active does not promote the standby — pause is a deliberate stop, not a failover.)

The endpoints

Both endpoints are POST and take no body. They're served by the runtime's HTTP server (runtime.http.enabled, default port 8080).

POST /pause

curl -X POST localhost:8080/pause
ResponseCodeBody
Pause initiated200{"status":"pausing","state":"DRAINING"}
Wrong state400{"error":"Cannot pause from state ..."}
Not initialized yet503{"error":"StoatFlow not initialized"}

Pause is only valid from RUNNING. Calling it during startup, while already paused, or while shutting down returns 400. The 200 response reports DRAINING — the instance reaches PAUSED a moment later once the drain completes; poll /state to confirm.

POST /unpause

curl -X POST localhost:8080/unpause
ResponseCodeBody
Resumed200{"status":"resumed","state":"RUNNING"}
Wrong state400{"error":"Cannot unpause from state ..."}
Not initialized yet503{"error":"StoatFlow not initialized"}

Unpause is valid from PAUSED or DRAINING. Calling it from RUNNING (or any other state) returns 400.

A maintenance sequence

A typical "pause, do something, resume" loop using the HTTP API:

# 1. Pause and watch it settle into PAUSED
curl -X POST localhost:8080/pause
#    -> {"status":"pausing","state":"DRAINING"}

# 2. Wait until it's fully drained (readiness flips DOWN; /state shows PAUSED)
until curl -s localhost:8080/state | grep -q '"PAUSED"'; do sleep 1; done

# ... perform maintenance on the downstream system ...

# 3. Resume
curl -X POST localhost:8080/unpause
#    -> {"status":"resumed","state":"RUNNING"}

The current lifecycle state is always available at /state.

Programmatic control (core)

The same pause/drain semantics are exposed as methods on the core StoatFlow handle — useful when you embed the engine via :core directly rather than driving it over HTTP. pause() returns once the drain has been initiated (state DRAINING); use awaitState(...) to block until PAUSED.

import io.stoatflow.core.StoatFlow
import java.time.Duration

stoatflow.pause()
stoatflow.awaitState(StoatFlow.State.PAUSED, Duration.ofSeconds(30))

// ... perform maintenance ...

stoatflow.unpause()

pause() throws IllegalStateException if the instance is not RUNNING; unpause() throws if it is not PAUSED or DRAINING — the same state rules the HTTP handlers turn into 400 responses.

When you run on the batteries-included :runtime (StoatFlowRuntime), pause and resume are driven over HTTP via /pause and /unpause. The programmatic pause() / unpause() / awaitState() methods above are on the underlying StoatFlow engine handle, which you hold directly when using the :core module.

When to use it

  • Downstream backpressure. A sink the topology writes to (a database, an external API, another topic's consumer) goes unavailable or slows down. Rather than letting failures pile up, pause, let the downstream recover, then unpause — the consumer session stays alive throughout, so you avoid a rebalance and a cold restart.
  • Short maintenance windows. You need to touch the broker, the downstream store, or the host briefly and want a clean, committed boundary. Pause drains and commits in-flight work first, so you resume from a consistent point with no partial in-flight state.

For anything longer-lived or anything that changes the topology or configuration, stop the instance and start it again — pause is for keeping a single instance warm across a brief, deliberate gap, not for long outages. See Production checklist for operational guidance.

Next steps

  • REST API — the full endpoint reference, including /state.
  • Health checks — how DRAINING / PAUSED map to liveness and readiness.
  • Probes — wiring the probes into Kubernetes so a paused instance isn't restarted.