Configuration reference
Every StoatFlow application is configured from a single application.yaml (loaded by StoatFlowRuntime.fromConfig(...)), plus environment-variable and system-property overrides. This page is a curated reference of the keys you are most likely to set, grouped by area, each with its type, default, and a one-line description.
runtime/src/main/resources/schemas/stoatflow-config-schema.json. IDEs (VS Code with the Red Hat YAML extension, IntelliJ) pick it up automatically for autocompletion and validation. This page omits a handful of advanced, fine-grained tuning knobs; the schema lists them all.For the configuration model — the four override layers, env-var naming, and how stoatflow.* maps onto the core engine — see Configuration model. For Kafka client passthrough, see Kafka client config.
How to read this page
- Key — the YAML path under
stoatflow:orruntime:. Nest by the dots (commit-barrier.interval-mslives understoatflow.commit-barrier.interval-ms). - Type — YAML scalar type or enum value set.
- Default — the value used when the key is omitted.
—means no default (the value must be set, or it is null/unset). - Defaults shown as expressions (e.g.
max(2, CPU cores)) are resolved at startup from the host.
${STOATFLOW_LICENSE_KEY}) — never hard-coded in committed YAML. See License configuration.stoatflow — top level
The required and most common engine keys.
| Key | Type | Default | Description |
|---|---|---|---|
application-id | string | — (required) | Unique application identifier; used for the consumer group and transactional id. |
bootstrap-servers | string | localhost:9092 | Kafka bootstrap servers (comma-separated). |
processing-guarantee | enum EXACTLY_ONCE | AT_LEAST_ONCE | EXACTLY_ONCE | Commit protocol. Exactly-once uses Kafka transactions; at-least-once uses non-transactional commit (lower latency, duplicates possible on crash recovery). |
default-key-serde | string (FQCN) | — | Fully qualified class name of the default key Serde when none is given explicitly. |
default-value-serde | string (FQCN) | — | Fully qualified class name of the default value Serde when none is given explicitly. |
schema-registry-url | string | — | Confluent Schema Registry URL. When set, propagated to serdes and enables the Schema Registry health check. |
deserialization-exception-handler | string (FQCN) | — | Handler class deciding CONTINUE vs FAIL on deserialization errors. |
production-exception-handler | string (FQCN) | — | Handler class for serialization and send errors. |
processing-exception-handler | string (FQCN) | — | Handler class for exceptions thrown by process(). |
rocks-db-config-setter | string (FQCN) | — | Custom RocksDBConfigSetter for advanced per-store RocksDB tuning. |
application-server | string (host:port) | localhost:<http.port> | Advertised host:port for interactive-query metadata (KS application.server). Single instance: identifies this instance as the active host for all stores. Accepts host:port, protocol://host:port or [ipv6]:port; a malformed value fails at startup (Kafka Streams 4.4 validates it the same way). |
See Error handling and DLQ for the built-in exception handler classes.
stoatflow.lanes
Key-affinity lane parallelism. See Lanes and parallelism.
| Key | Type | Default | Description |
|---|---|---|---|
lanes.count | integer | max(2, CPU cores) | Number of key-affinity lanes (parallel processing units). |
lanes.queue-capacity | integer | 300 | Capacity of each lane's queue; controls backpressure. |
lanes.thread-type | enum VIRTUAL | PLATFORM | VIRTUAL | Lane thread type. Virtual (Project Loom) by default; platform threads available for the JNI RocksDB backend. |
stoatflow.commit-barrier
Commit-barrier cadence and the bounds the engine self-tunes within. The interval between barriers is adaptive at runtime; you set the seed and the floor/ceiling. See Exactly-once.
| Key | Type | Default | Description |
|---|---|---|---|
commit-barrier.interval-ms | integer | 500 | Seed interval between commit barriers; the initial value before adaptive scheduling takes over. |
commit-barrier.min-interval-ms | integer | 150 | Lower bound on the barrier-to-barrier interval. |
commit-barrier.max-interval-ms | integer | 5000 | Upper bound on the barrier-to-barrier interval. |
commit-barrier.timeout-ms | integer | 60000 | Timeout for barrier completion. Bounds the commit critical path and cascades to the producer's transaction.timeout.ms, max.block.ms, and delivery.timeout.ms. Must be greater than interval-ms. |
commit-barrier.dedicated-commit-thread | boolean | true | Run commits on a dedicated thread so a lane is not stalled during the Kafka transaction commit and state flush. |
commit-barrier.max-epoch-records | integer | — (no cap) | Optional hard upper bound on records committed per epoch. Unset means the engine sizes epochs adaptively within the interval bounds above. |
commit-barrier.max-engine-restarts | integer | 5 | Max fault-triggered in-place engine restarts within the rolling window below before the instance escalates to a terminal shutdown. 0 disables in-place restart recovery. |
commit-barrier.engine-restart-window-ms | integer | 300000 (5 min) | Rolling window over which fault-triggered engine restarts are counted. Bounds a recurring fault to max-engine-restarts restarts per window instead of looping. |
commit-barrier.max-poison-replays | integer | 10 | Max poison-epoch replays within the rolling window below before the instance shuts down with the source offsets held. A replay is an in-place engine restart that reprocesses an aborted epoch with the poison records skipped. 0 disables replay entirely. Separate from max-engine-restarts, and in-memory — exhausting it ends the process, so the pod restarts with a clean budget. |
commit-barrier.poison-replay-window-ms | integer | 60000 (1 min) | Rolling window over which poison-epoch replays are counted. A poison that does not converge — typically one derived from accumulated state — exhausts the budget and terminates the instance rather than replaying forever. |
stoatflow.state
State-store storage, caching, and restoration. See State stores and Migration (with data).
| Key | Type | Default | Description |
|---|---|---|---|
state.dir | string | ${java.io.tmpdir}/stoatflow | Directory for state-store data. Put it on fast local storage (SSD); in Kubernetes, on a persistent volume. |
state.uncommitted-max-bytes | integer (bytes) | 268435456 (256 MiB) | Global cap on uncommitted state bytes across all stores, the timer store included; exceeding it triggers an early commit barrier. |
state.restoration-enabled | boolean | true | Restore state from changelog topics on startup when local state is missing. |
state.restoration-pool-size | integer | CPU cores × 2 | Restoration worker pool size; each worker uses its own consumer for parallel fetch. |
state.restoration-batch-size | integer | 20000 | Records per RocksDB write batch during restoration. |
state.sst-restoration-enabled | boolean | false | Opt-in SST-file ingestion for full cold-start KV restoration (bypasses memtable/L0). |
state.parallel-store-commits | boolean | true | Commit multiple stores in parallel at barrier time. |
state.parallel-store-commit-threads | integer | CPU cores | Max platform threads for parallel store commits (used only with 2+ transactional stores). |
state.cleanup-delay-ms | integer | 600000 (10 min) | Delay before deleting RocksDB directories for stores no longer in the topology. 0 = immediate, -1 = never delete (detect + warn only). |
state.cleanup-dir-max-age-ms | integer (ms) | -1 (disabled) | Age threshold for purging local per-store state directories untouched for at least this long at startup (KIP-1259); they are rebuilt from the changelog. Set ≥ the changelog's delete.retention.ms. Distinct from cleanup-delay-ms (orphaned-store cleanup). |
state.segment-checkpoint-records | integer | 100000 | Periodic checkpoint cadence for segmented window/session stores, by record count. These stores run with the WAL off and become durable only on close(), so a checkpoint (flush dirty segments, then the offsets) bounds how much of the changelog an ungraceful restart — or a hot-standby promotion — has to re-apply. Whichever-first with state.segment-checkpoint-ms. Deliberately coarse to limit SST/compaction churn. |
state.segment-checkpoint-ms | integer (ms) | 30000 (30 s) | The same checkpoint cadence by wall-clock time; whichever-first with state.segment-checkpoint-records. |
state.format-downgrade | enum refuse | wipe-and-restore | refuse | What to do when a store's on-disk format is newer than the topology asks for — today, a headers-aware store (KIP-1271) opened with record headers turned off. refuse fails startup with an actionable message and touches nothing on disk. wipe-and-restore acknowledges the downgrade: that store's local state is deleted and rebuilt from its changelog (one full restore). An empty headers keyspace is dropped in place for free either way; a store with no recovery source is refused either way. See State stores. |
state.layout-mismatch | enum refuse | wipe-and-restore | refuse | What to do when a windowed or session store's on-disk layout no longer describes what the topology asks for — a changed window size, a plain↔timestamped switch, a store-kind change, or a changed segment interval on a hand-built non-segmented store, where the segment id is part of every key. Each of those would be misread rather than fail, so it is detected before the store opens. A store that can be rebuilt is always rebuilt automatically from its changelog, and this setting is not consulted; it applies only to a store with no recovery source (withLoggingDisabled(), or the global changelog disabled), where the local copy is the only copy. refuse fails startup naming every affected store and the field that moved; wipe-and-restore accepts the loss and starts those stores empty. Distinct from state.format-downgrade, which governs a record-headers downgrade. See Changing a store's retention. |
state.versioned.sweep-period-ms | integer | 900000 | Target time for one full reclamation pass over a versioned (KIP-889) store. Versioned-store retention is a minimum bound: an out-of-window point-in-time read stops answering immediately, but the disk space is reclaimed by a rolling sweep that runs a slice per commit. This is that SLA — longer costs less read bandwidth and leaves expired versions on disk longer. Watch the result with stoatflow.store.versioned.sweep.lag.ms. |
state.versioned.max-scan-bytes-per-barrier | integer | 8388608 | Primary budget cap: bytes the sweep may read per commit, per sub-topology — shared across every versioned store on that sub-topology, along with the entry and deletion caps, so adding stores does not multiply the per-commit cost. Raise it if stoatflow.store.versioned.sweep.budget.exhausted.total climbs steadily, which means the configured period is not achievable at your store size. Minimum 65536: the sweep floors each store's derived slice there, so a smaller lane budget has no valid answer and is rejected at start-up rather than silently disabling reclamation. |
state.versioned.max-scan-entries-per-barrier | integer | 50000 | Secondary cap, for stores whose values are small enough that per-entry CPU dominates instead of bytes. |
state.versioned.max-deletions-per-barrier | integer | 10000 | Caps reclamation deletes added to one transaction. They count toward state.uncommitted-max-bytes, so raising this substantially can start triggering memory-pressure commits. |
Advanced SST knobs (state.sst-buffer-max-bytes, state.sst-flush-interval-files) and state.flush-min-entries-per-chunk are in the schema.
stoatflow.changelog
Changelog topics that back state stores. See State stores.
| Key | Type | Default | Description |
|---|---|---|---|
changelog.enabled | boolean | true | Write state mutations to Kafka changelog topics within the transaction. |
changelog.create-topics-if-not-exist | boolean | true | Auto-create changelog topics (with cleanup.policy=compact) when missing. |
changelog.replication-factor | integer | -1 (broker default) | Replication factor for auto-created changelog topics. |
changelog.num-partitions | integer | 3 | Partition count for auto-created changelog topics. A changelog's partition count sets its write-throughput ceiling and decides how the store's write load shards across brokers — one partition funnels every write for that store through a single broker leader. Unlike Kafka Streams, where co-partitioning forces this to equal the source topic's partition count, StoatFlow's is decoupled and is therefore a real choice. Faster cold-start restore is a secondary, sub-linear effect. The cost is paid on every commit, because changelog records ride the same transaction as output records. Prefer small counts and raise a specific store only on a measurement, using Materialized.withChangelogNumPartitions(n) and its siblings on StreamJoined, the Processor-API StoreBuilder and Suppressed.BufferConfig — which also let a small store be pinned back down to 1. Fixed when the topic is created: changing it later is a no-op plus a startup warning, and increasing it by hand on a compacted changelog corrupts state. |
changelog.timer-num-partitions | integer | -1 | Partition count for the timer store's changelog topic. -1 inherits changelog.num-partitions; any positive value overrides it for the timer store alone. Timers are not materialized through the DSL, so a config key is the only surface for this. |
changelog.reconcile-topic-configs | boolean | true | Bring an existing changelog topic's cleanup.policy and retention.ms back in line with what its store asks for, on every start. Per-store topic settings used to be applied only when StoatFlow created the topic, so a changelog created by an older StoatFlow, by the migration tool, or under a different retention kept its original settings forever and never shed anything. Only those two keys are touched, and only for window and session stores — a plain key-value store's changelog is left alone, and so is a versioned store's, because in both cases a key's last value must survive regardless of age. retention.ms is only ever widened: a larger value you set yourself (through withLoggingEnabled(...) or out of band) is kept and the decision logged, because the comparison is against the broker and cannot tell your value from StoatFlow's, and narrowing a compact,delete topic makes the broker drop segments. Runs whether or not changelog.create-topics-if-not-exist is enabled — creating a topic and altering one are different permissions. If the broker denies alterConfigs, StoatFlow logs a warning and starts anyway. Set false to leave existing topics exactly as they are. |
stoatflow.watermark
Event-time watermark generation. See Event time and watermarks.
| Key | Type | Default | Description |
|---|---|---|---|
watermark.max-out-of-orderness-ms | integer | 10000 | Allowed out-of-orderness before an event is considered late. |
watermark.idleness-timeout-ms | integer | 300000 (5 min) | Partitions idle for this long are excluded from the global watermark. The default for sources that do not set their own WatermarkStrategy.withIdleness(...), which overrides it per source. 0 disables idleness detection for those sources: one of their partitions that never receives a record then pins the global watermark forever and no window or session store expires (segments accumulate without bound — watch stoatflow.store.segments); one WARN is logged at start-up naming the sources left uncovered. |
watermark.auto-interval-ms | integer | 200 | Periodic watermark emission interval. |
stoatflow.processing
Processing behavior. The keys below are the public, commonly-set ones; the schema additionally exposes a number of low-level dispatch/yield tuning knobs that should normally be left at defaults.
| Key | Type | Default | Description |
|---|---|---|---|
processing.mdc-enabled | boolean | false | Populate logging MDC keys (laneId, sourceTopic, sourcePartition, sourceOffset) during processing. |
processing.max-task-idle-ms | integer | 0 | Wait time for empty partition buffers to preserve cross-partition timestamp order. -1 = process immediately, 0 = wait only while broker has lag, >0 = extra wait. |
processing.buffered-records-per-partition | integer | 2000 | Records buffered per partition at which that partition's consumption is paused — a pause threshold, so a partition peaks at this value − 1 + max.poll.records. |
processing.consumer-poll-timeout-ms | integer | 20 | Consumer poll() fallback timeout. |
processing.dispatch-mode | enum AUTO | DIRECT | LANE | LANE | Record dispatch mode. LANE routes via key-affinity lanes; AUTO may pick DIRECT for simple stateless topologies; DIRECT is experimental. |
processing.parallel-deser-mode | enum AUTO | ON | OFF | AUTO | Parallel deserialization. AUTO enables it when per-record cost is high enough; ON/OFF force it. |
processing.timestamp-coordination-mode | enum AUTO | ENABLED | DISABLED | AUTO | Event-time coordination. AUTO derives from topology traits; override only for debugging. DISABLED is refused at startup for a topology with windowed aggregations, suppress or stream-stream joins. |
processing.timer-lane-affinity-sampled-check | enum off | warn | error | warn | Verdict of the sampled timer lane-affinity check: the first 64 records per Processor API processor and lane, then 1 in 4,096, re-serialize the record key with the processor's timer key serde and compare the lane those bytes pick with the record's lane. warn logs once per processor and counts stoatflow.timer.lane.mismatch.total; error stops the application with TimerLaneAffinityException; off does not sample. warn by default because the sample also judges processors that never register a timer for the record's own key, and some wire encodings legitimately differ from a re-serialization (an evolved Schema Registry id, JSON from another producer). A timer registered or deleted for the record's own key is judged by processing.timer-lane-affinity-guard instead. Core key timer.lane-affinity.sampled-check. See Timer key serde. |
processing.timer-lane-affinity-guard | enum off | warn | error | error | Verdict of the timer lane-affinity guard: a Processor API timer registered or deleted for the key of the record being processed whose bytes, serialized with the processor's timer key serde, pick another lane than the record's — a proven split, so the key's timer callbacks would race its records. error stops the application with TimerLaneAffinityException; warn logs once per processor, counts stoatflow.timer.lane.mismatch.total and registers the timer; off does not check. Bytes that differ but land on the same lane are never reported. Core key timer.lane-affinity.guard. See Timer key serde. |
processing.wall-clock-advance-interval-ms | integer | 10 | Cadence for wall-clock punctuators and scheduled sources. |
processing.state-transition-history-size | integer | 20 | Number of past app-state transitions retained for the /state endpoint. |
stoatflow.validation
Startup and single-instance safety checks. See How StoatFlow differs from KS.
| Key | Type | Default | Description |
|---|---|---|---|
validation.ensure-explicit-naming | boolean | true | Fail startup if any topology operation uses an auto-generated name (KIP-1111). |
validation.validate-complete-assignment | boolean | true | Wait for and verify that all source-topic partitions are assigned at startup. |
validation.partition-assignment-timeout-ms | integer | 60000 | Timeout to wait for complete partition assignment after subscribe. |
validation.pre-flight-consumer-group-check | boolean | false | Check for active consumer-group members before starting (prevents accidental duplicate instances). |
stoatflow.shutdown
Graceful shutdown behavior. See Probes.
| Key | Type | Default | Description |
|---|---|---|---|
shutdown.timeout-ms | integer | 25000 | Timeout for graceful shutdown; lets in-flight barriers complete. |
shutdown.leave-group-on-close | boolean | false | Whether the consumer sends a LeaveGroup on close. Disabled (with static membership) gives faster restart reassignment. Also what CloseOptions.GroupMembershipOperation.DEFAULT — the operation close() passes — resolves to. |
shutdown.hard-exit-on-shutdown-hang | boolean | true | If graceful shutdown stalls past its budget, force a clean process exit so Kubernetes restarts the pod. |
shutdown.hard-exit-budget-ms | integer | 2 × shutdown.timeout-ms | Budget before the hard-exit fallback fires. Must be ≥ shutdown.timeout-ms. |
stoatflow.commit-stall
Commit-pipeline freeze detection. These bounds also drive the liveness probe's stall check. See Probes.
| Key | Type | Default | Description |
|---|---|---|---|
commit-stall.threshold-ms | integer | 45000 | If commit work stalls longer than this, a thread dump is captured, graceful shutdown begins, and /health/live returns DOWN. Must be less than commit-barrier.timeout-ms. 0 disables. |
commit-stall.poll-interval-ms | integer | 5000 | How often the stall check runs. |
commit-stall.dump-enabled | boolean | true | Emit a thread dump to logs when a stall is detected. |
stoatflow.rocks-db
RocksDB preset and backend. See State stores.
| Key | Type | Default | Description |
|---|---|---|---|
rocks-db.preset | enum DEFAULT | LOW_MEMORY | HIGH_PERFORMANCE | DEFAULT | Memory preset. DEFAULT ≈ 256 MiB, LOW_MEMORY ≈ 64 MiB, HIGH_PERFORMANCE ≈ 1 GiB. |
rocks-db.backend | enum AUTO | FFM | JNI | AUTO | RocksDB binding. AUTO picks the faster path per runtime — FFM on the JVM, JNI under a GraalVM native image. FFM has lower per-call overhead on HotSpot; JNI uses the rocksdbjni bindings. |
stoatflow.caching
Downstream emission suppression for KTables. See Aggregations.
| Key | Type | Default | Description |
|---|---|---|---|
caching.max-entries | integer | 2147483647 (unbounded) | Per-store cached-entry cap before an early barrier. Unbounded by default so it never bites high-cardinality workloads — bytes are the default lever; set a finite value for a count-based bound. |
caching.max-estimated-bytes | integer (bytes) | 268435456 (256 MiB) | Max estimated cached bytes per store before an early barrier. |
stoatflow.timer-backend
Timer storage backend for the Processor API. See Processor API.
| Key | Type | Default | Description |
|---|---|---|---|
timer-backend.type | enum HEAP | PERSISTENT | HYBRID | HYBRID | HEAP = in-memory (fast, unbounded), PERSISTENT = RocksDB only (memory-bounded), HYBRID = heap cache + RocksDB. |
timer-backend.changelog-enabled | boolean | true | Log timer mutations to a Kafka changelog for durability (applies to all types). |
timer-backend.cache-horizon-ms | integer | 60000 | HYBRID only: how far ahead timers are cached in the heap. Must be > load-interval-ms. |
timer-backend.load-interval-ms | integer | 10000 | HYBRID only: how often the loader pulls upcoming timers from RocksDB. Must be < cache-horizon-ms. |
stoatflow.ktable / stoatflow.fk-join / stoatflow.dsl / stoatflow.topology
| Key | Type | Default | Description |
|---|---|---|---|
ktable.auto-reuse-source-topics | boolean | true | Reuse compacted KTable source topics for restoration instead of separate changelog topics. |
fk-join.use-reverse-index-cache | boolean | true | Maintain an in-memory reverse index for O(1) FK-join right-side lookups (disable to save heap on very large FK tables). |
dsl.store-format | enum DEFAULT | HEADERS | DEFAULT | Global default on-disk format for DSL-materialized stores (KIP-1285). HEADERS makes the table (table()/toTable()/globalTable()), KTable operator, table-table and foreign-key join result, and aggregation stores persist record headers (KIP-1271); engine-internal stores (FK subscription, suppress buffers, stream-stream join buffers) stay plain. Per-store Materialized.withRecordHeaders() / withoutRecordHeaders() always wins. |
topology.validation.<rule-id> | enum off | warn | error | per rule | Per-rule topology validation, raised at topology-compile time — so error fails StoatFlow.start() and TopologyTestDriver, with no broker, reporting every offending site in one message rather than failing on the first. Rules: unresolved-boundary-key-serde (default warn) — a sub-topology boundary resolved no key serde for the key crossing it, so the configured default is used for lane-assignment hashing; inherited-boundary-key-serde (default warn) — the boundary resolved only by inheriting a serde from before the re-key (sound for lane hashing, but only works if the key types line up). papi-key-affinity-presumed (default error) — a proven re-key feeds a Processor API node connecting a writable store with no boundary between them under topology.processor-api-key-affinity: off, so records reach it on the lane of their old key and a store read-modify-write straddling a commit barrier can lose an update durably; no lock closes that window, which is why this one refuses the build rather than warning. Read-only (KIP-813) stores are excluded — they cannot be written. papi-key-affinity-presumed-chain (default warn) — the same shape where the feeder is itself a Processor API node: process() and addProcessor() declare a key change unconditionally, so the re-key may not exist at all, and Kafka Streams compiles and runs such chains without a repartition. timer-key-lane-affinity (default warn) — a Processor API node registering keyed timers whose timer key bytes may differ from the bytes its records are laned by: behind a source that declares only a key deserializer, or entered across a boundary laned by another key serde, so a key's timers may run on another lane than its records. Declare the key serde with addSource(name, Consumed.with(...), topics), or for a boundary the same key serde on the operator that opens it (Repartitioned / Grouped); at runtime a timer for the record's own key that picks another lane follows processing.timer-lane-affinity-guard (default error), and the sampled check follows processing.timer-lane-affinity-sampled-check. declared-serde-over-default (default off) — a DSL operator where StoatFlow writes bytes with a serde declared upstream while Kafka Streams would use the configured default, and the two differ: groupByKey(Grouped) without serdes, a stream-stream join without StreamJoined serdes (its stores and its output key), a cogroup without a Materialized key serde, merge, a KTable filter/mapValues/transformValues store with a Materialized that sets no serde — each reported where the bytes are written (a sink without Produced, a table, an aggregation store); a windowed key always differs (KAFKA-9259). Off for a new application; set it to error before a state-carrying migration from Kafka Streams, so the build lists every site whose bytes would differ from what Kafka Streams wrote (see Serdes). alignment-idleness-off (default error) — a source with watermark alignment has no idleness timeout (the global timeout is 0 with no withIdleness(...), a custom idlenessTimeout() of zero, or withIdleness(FOREVER)), so a lagging source that never sends again would hold every source for good; add withIdleness(...) to the group's sources. alignment-mixed-watermark-bounds (default warn) — sources of one alignment group whose out-of-orderness bounds differ by more than a source's drift, so the smaller-bound source stays held (see Watermark alignment). presumed-cross-lane-read (default warn) — under exactly_once, an lruMap or in-memory versioned store that a Processor API node may read across lanes: from a punctuator (or a node downstream of one in the same sub-topology) while record lanes write it, or from another sub-topology than its writer; a global or read-only such store counts as read by every lane. Such a store cannot be read as of the reading lane's commit, so the read could see another lane's uncommitted write. Materialise it with a default store supplier (for a versioned table, Stores.persistentVersionedKeyValueStore) or run at_least_once; set the rule to off if the processor never reads it that way. The same read by a DSL join is refused. presumed-cross-sub-topology-write (default warn) — Processor API nodes connect one store from more than one sub-topology (a re-key between them splits them), under either guarantee: writers on lanes that cross commit barriers at different times can lose an update. Kafka Streams forces such processors into one subtopology (KAFKA-20464); StoatFlow places nodes by key. A node that only reads the store loses nothing — set the rule to off then. The same shape among DSL writers is refused. papi-key-affinity-presumed and alignment-idleness-off default to error, declared-serde-over-default to off, everything else to warn — so the only upgrade-time build failures these rules can add are a store-connected Processor API node behind a proven re-key, and watermark alignment without idleness. An unknown rule id is rejected at startup. See Boundary key serdes, Key affinity after a re-key, Timer key serde, Sharing a store between processors and Reading a store from another lane. |
topology.sub-topology-split | enum lazy | eager | lazy | Where the compiler opens sub-topology boundaries. lazy opens one only where a downstream operator genuinely requires the re-keyed record's lane affinity — grouped aggregations, joins, toTable(), plus every explicit repartition(); this is the Kafka Streams repartitionRequired model, so describe() matches a KS original. eager restores the pre-1.0.0 boundary at every key-changing node. Changing this renumbers sub-topology ids, which appear in thread names, the sub_topology metric tag and the topology endpoints. See Lanes and parallelism — in particular the skew note, if you relied on a re-key to spread work. |
topology.processor-api-key-affinity | enum off | presumed | off | Whether a Processor API node's key affinity is presumed from the adapter's static shape. off matches Kafka Streams, which never materialises a repartition before process() / processValues() even with connected stores. presumed restores the pre-1.0.0 boundary. Inert under topology.sub-topology-split: eager. After a many-to-one re-key, off gives up key affinity at that node — see Key affinity after a re-key; the provable store-connected case is refused at build time by the papi-key-affinity-presumed validation rule. off also leaves the node in its upstream sub-topology, so it inherits that sub-topology's lane count instead of starting its own — re-check any deliberate per-sub-topology numberOfLanes override. |
stoatflow.kafka
Kafka client passthrough. Values under these maps are merged on top of StoatFlow's framework defaults; forced overrides (e.g. bootstrap.servers, group.id, enable.auto.commit=false, allow.auto.create.topics=false) cannot be changed. Full resolution order in Kafka client config.
| Key | Type | Default | Description |
|---|---|---|---|
kafka.consumer | map<string,string> | {} | Additional consumer properties (e.g. auto.offset.reset, max.poll.records). Its security.* / ssl.* / sasl.* entries also propagate to the producer, admin and restoration clients. |
kafka.producer | map<string,string> | {} | Additional producer properties (e.g. compression.type, linger.ms). |
kafka.restoration-consumer | map<string,string> | {} | Overrides for the restoration consumer only. |
kafka.admin | map<string,string> | {} | Overrides for the admin client (changelog topic management, broker health check, consumer-group preflight). Only needed when the admin principal differs from the consumer's — the consumer's security settings already propagate. |
stoatflow.license
Runtime license. See License configuration.
| Key | Type | Default | Description |
|---|---|---|---|
license.key | string | — | License key string. Use ${STOATFLOW_LICENSE_KEY} interpolation; do not hard-code. |
license.file | string | — | Path to a chmod 600 file holding the key on one line. Mutually exclusive with key (key wins). |
license.environment | string | — | Deployment environment label feeding the machine fingerprint. Required for the Production tier. |
license.cache-dir | string | ~/.stoatflow/license-cache/ | Offline-cache directory. In Kubernetes, mount on a persistent volume so it survives restarts. |
license.verbose-banner | boolean | false | Include the customer name in the startup banner. |
-D system properties (STOATFLOW_LICENSE_*) take precedence over these YAML values.stoatflow.ha
Opt-in hot-standby high availability. With mode: off (default) the application is a single instance. See High availability.
| Key | Type | Default | Description |
|---|---|---|---|
ha.mode | enum | off | off (single instance) or active-standby (hot-standby cluster: one active + one or more warm standbys). |
ha.pod-id | string | POD_NAME → HOSTNAME → hostname | Stable per-pod identity distinguishing the peers. |
ha.coordination-topic | string | __stoatflow_<applicationId>_ha | Single-partition, compacted, auto-created topic the cluster coordinates through (heartbeats + the promotion token). One partition is the required shape; a pre-existing topic with a different count is warned about at startup (reads and writes are both pinned to partition 0, so it still works). |
ha.desired-standbys | integer | 1 | Caught-up standby spares the readiness gate protects. 1 is the classic active/passive pair; raise it (with replicas) for more redundancy. Drives the redundancy floor on rolling deploys. |
ha.max-standbys | integer | 3 | Ceiling on standbys, bounding changelog fan-out (each standby is one more changelog reader). Total instances ≤ max-standbys + 1. |
ha.failover-priority | integer | 0 | Per-pod tie-break hint ordering candidates within the equal-lag bucket (higher wins). Never overrides lag or the hash floor. |
ha.promotion-settle-window-ms | integer | 500 | After claiming the promotion token, how long the winner confirms it is the sole claimant before fencing (single-promoter guarantee). Bounded 1..ha.staleness-threshold-ms. |
ha.heartbeat-ms | integer | 1000 | How often each pod publishes its liveness and role. |
ha.staleness-threshold-ms | integer | 5000 | Gap without a peer heartbeat before it's considered stale. Must be ≥ ha.heartbeat-ms. |
ha.staleness-misses | integer | 3 | Consecutive missed checks before a standby is elected to promote (debounce). |
ha.acceptable-recovery-lag | integer | 50000 | Total committed-changelog lag (records, summed across all changelog partitions) at/below which a standby is caught up (READY_STANDBY) and promotion-eligible without the grace fallback. A single total sum, not per-task like KS's acceptable.recovery.lag (10000). Correctness never depends on it (restore-before-process does); mostly a raise-me lever. |
ha.promotion-grace-ms | integer | 5000 | Grace window after which the least-behind standby auto-promotes when no standby is within acceptable-recovery-lag (availability-first; eligibility re-checked continuously). |
ha.promotion-restore-no-progress-timeout-ms | integer | 60000 | Progress-aware deadline for restore-before-process — fails only on a window with zero records applied (genuinely stuck), then self-terminates → K8s restart. Not the commit-path barrier timeout. |
runtime.http
The built-in HTTP server. See REST API.
| Key | Type | Default | Description |
|---|---|---|---|
runtime.http.enabled | boolean | true | Enable the HTTP server (health, metrics, info, topology, control endpoints). |
runtime.http.port | integer | 8080 | Port to bind. |
runtime.http.host | string | 0.0.0.0 | Bind address. |
runtime.http.backlog | integer | 50 | Max pending connections in the socket backlog. |
runtime.http.debug.enabled | boolean | true | Register the /debug/threads and /debug/barriers diagnostic endpoints. Disable to reduce attack surface in hardened deployments. |
runtime.metrics
Micrometer / Prometheus metrics. See Metrics.
| Key | Type | Default | Description |
|---|---|---|---|
runtime.metrics.enabled | boolean | true | Enable metrics collection and the /metrics Prometheus endpoint. |
runtime.metrics.prefix | string | stoatflow | Prefix for all metric names. |
runtime.metrics.recording-level | enum info | debug | trace | info | Metric detail level. info is production-safe; higher levels add cardinality/overhead. |
runtime.metrics.common-tags | map<string,string> | {} | Tags applied to all metrics (e.g. env, region). |
runtime.metrics.bind-jvm-metrics | boolean | true | Bind JVM metrics (memory, GC, threads). |
runtime.metrics.naming | enum stoatflow | kafka-streams | both | stoatflow | Metric-name families on /metrics. both = native + KS-named (migration posture); kafka-streams hides the mapped native families. See Reusing your Kafka Streams dashboards. |
runtime.metrics.ks-compat.shape | enum micrometer-binder | jmx-exporter | micrometer-binder | Prometheus shape for the KS-named series. Read only when naming ≠ stoatflow. |
runtime.metrics.ks-compat.sample-window-ms | integer | 30000 | Trailing window for KS -rate/-avg/-max twins (min 5000). |
runtime.metrics.ks-compat.kafka-version | string | 4.1.0 | Synthetic kafka_version tag value (micrometer-binder shape). |
runtime.metrics.ks-compat.thread-id | string | (auto) | Override for the synthetic KS thread-id (default <application-id>-StreamThread-1). |
runtime.metrics.ks-compat.client-id | string | (auto) | Override for the synthetic KS client-id. |
runtime.endpoints
Visibility of the /info and /config endpoints. See REST API.
| Key | Type | Default | Description |
|---|---|---|---|
runtime.endpoints.config.enabled | boolean | true | Enable the /config endpoint (merged config with sensitive values masked). |
runtime.endpoints.info.show-app | boolean | true | Include application info (applicationId) in /info. |
runtime.endpoints.info.show-java | boolean | true | Include Java runtime info in /info. |
runtime.endpoints.info.show-kafka | boolean | true | Include Kafka config (bootstrap servers) in /info. |
runtime.endpoints.info.show-uptime | boolean | true | Include uptime in /info. |
runtime.endpoints.info.show-state | boolean | true | Include state + transition history in /info. |
runtime.endpoints.info.show-watermarks | boolean | true | Include watermark info in /info. |
runtime.endpoints.info.show-endpoints | boolean | true | Include the list of available endpoints in /info. |
runtime.endpoints.info.show-offsets | boolean | true | Include the consumer lag summary (totalLag, per-topic lags) in /info. |
runtime.endpoints.info.show-consumer | boolean | true | Include the consumer group summary (groupId, subscription, partitions) in /info. |
runtime.health
Health-check timeouts. See Health checks.
| Key | Type | Default | Description |
|---|---|---|---|
runtime.health.kafka-broker.timeout-ms | integer | 2000 | Timeout for the Kafka broker connectivity check. |
runtime.health.schema-registry.timeout-ms | integer | 5000 | Timeout for the Schema Registry check (auto-enabled when schema-registry-url is set). |
logging
Per-package log levels applied at startup (overrides logback.xml).
| Key | Type | Default | Description |
|---|---|---|---|
logging.level | map<string, enum> | {} | Map of logger name → level (TRACE/DEBUG/INFO/WARN/ERROR/OFF). Use ROOT for the root logger. |
logging:
level:
ROOT: INFO
io.stoatflow: DEBUG
org.apache.kafka: WARN
Example
A representative application.yaml touching the common keys:
stoatflow:
application-id: order-processor
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:-localhost:9092}
processing-guarantee: EXACTLY_ONCE
license:
key: ${STOATFLOW_LICENSE_KEY}
environment: ${DEPLOY_ENV:-dev}
lanes:
count: 16
state:
dir: /var/lib/stoatflow/state
rocks-db:
preset: DEFAULT
runtime:
http:
enabled: true
port: ${HTTP_PORT:-8080}
metrics:
enabled: true
common-tags:
env: ${DEPLOY_ENV:-dev}
Related
Configuration model
Override layers, env-var naming, and YAML-to-engine mapping.
Kafka client config
Consumer/producer passthrough, framework defaults, forced overrides.
Defaults and presets
What StoatFlow sets out of the box and the RocksDB presets.
Tuning
Which knobs to turn for throughput, latency, and memory.
Reference
Lookup material for StoatFlow — configuration keys, REST API, the Gradle plugin and Maven build references, the Kafka Streams compatibility matrix, the metrics catalogue, and the glossary.
REST API reference
Every HTTP endpoint the StoatFlow runtime exposes — path, method, purpose, response shape, content negotiation, status codes, and gating.