Configuration reference

Curated reference of the StoatFlow configuration keys — stoatflow.* (engine) and runtime.* (HTTP, metrics, health) — with type, default, and a one-line description per key.

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.

The authoritative, always-current list of every key — including bound constraints, enums, and examples — is the JSON Schema shipped in the runtime artifact: 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: or runtime:. Nest by the dots (commit-barrier.interval-ms lives under stoatflow.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.
Secrets (the license key, Kafka SASL passwords, Schema Registry credentials) should be supplied via environment-variable interpolation (${STOATFLOW_LICENSE_KEY}) — never hard-coded in committed YAML. See License configuration.

stoatflow — top level

The required and most common engine keys.

KeyTypeDefaultDescription
application-idstring— (required)Unique application identifier; used for the consumer group and transactional id.
bootstrap-serversstringlocalhost:9092Kafka bootstrap servers (comma-separated).
processing-guaranteeenum EXACTLY_ONCE | AT_LEAST_ONCEEXACTLY_ONCECommit protocol. Exactly-once uses Kafka transactions; at-least-once uses non-transactional commit (lower latency, duplicates possible on crash recovery).
default-key-serdestring (FQCN)—Fully qualified class name of the default key Serde when none is given explicitly.
default-value-serdestring (FQCN)—Fully qualified class name of the default value Serde when none is given explicitly.
schema-registry-urlstring—Confluent Schema Registry URL. When set, propagated to serdes and enables the Schema Registry health check.
deserialization-exception-handlerstring (FQCN)—Handler class deciding CONTINUE vs FAIL on deserialization errors.
production-exception-handlerstring (FQCN)—Handler class for serialization and send errors.
processing-exception-handlerstring (FQCN)—Handler class for exceptions thrown by process().
rocks-db-config-setterstring (FQCN)—Custom RocksDBConfigSetter for advanced per-store RocksDB tuning.
application-serverstring (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.

KeyTypeDefaultDescription
lanes.countintegermax(2, CPU cores)Number of key-affinity lanes (parallel processing units).
lanes.queue-capacityinteger300Capacity of each lane's queue; controls backpressure.
lanes.thread-typeenum VIRTUAL | PLATFORMVIRTUALLane 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.

KeyTypeDefaultDescription
commit-barrier.interval-msinteger500Seed interval between commit barriers; the initial value before adaptive scheduling takes over.
commit-barrier.min-interval-msinteger150Lower bound on the barrier-to-barrier interval.
commit-barrier.max-interval-msinteger5000Upper bound on the barrier-to-barrier interval.
commit-barrier.timeout-msinteger60000Timeout 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-threadbooleantrueRun commits on a dedicated thread so a lane is not stalled during the Kafka transaction commit and state flush.
commit-barrier.max-epoch-recordsinteger— (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-restartsinteger5Max 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-msinteger300000 (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-replaysinteger10Max 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-msinteger60000 (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.
The schema exposes additional fine-grained commit-barrier tuning factors (smoothing and growth parameters for the adaptive epoch sizer). These rarely need changing — leave them at their defaults unless directed by support. They are listed in full in the JSON schema.

stoatflow.state

State-store storage, caching, and restoration. See State stores and Migration (with data).

KeyTypeDefaultDescription
state.dirstring${java.io.tmpdir}/stoatflowDirectory for state-store data. Put it on fast local storage (SSD); in Kubernetes, on a persistent volume.
state.uncommitted-max-bytesinteger (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-enabledbooleantrueRestore state from changelog topics on startup when local state is missing.
state.restoration-pool-sizeintegerCPU cores × 2Restoration worker pool size; each worker uses its own consumer for parallel fetch.
state.restoration-batch-sizeinteger20000Records per RocksDB write batch during restoration.
state.sst-restoration-enabledbooleanfalseOpt-in SST-file ingestion for full cold-start KV restoration (bypasses memtable/L0).
state.parallel-store-commitsbooleantrueCommit multiple stores in parallel at barrier time.
state.parallel-store-commit-threadsintegerCPU coresMax platform threads for parallel store commits (used only with 2+ transactional stores).
state.cleanup-delay-msinteger600000 (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-msinteger (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-recordsinteger100000Periodic 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-msinteger (ms)30000 (30 s)The same checkpoint cadence by wall-clock time; whichever-first with state.segment-checkpoint-records.
state.format-downgradeenum refuse | wipe-and-restorerefuseWhat 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-mismatchenum refuse | wipe-and-restorerefuseWhat 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-msinteger900000Target 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-barrierinteger8388608Primary 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-barrierinteger50000Secondary cap, for stores whose values are small enough that per-entry CPU dominates instead of bytes.
state.versioned.max-deletions-per-barrierinteger10000Caps 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.

KeyTypeDefaultDescription
changelog.enabledbooleantrueWrite state mutations to Kafka changelog topics within the transaction.
changelog.create-topics-if-not-existbooleantrueAuto-create changelog topics (with cleanup.policy=compact) when missing.
changelog.replication-factorinteger-1 (broker default)Replication factor for auto-created changelog topics.
changelog.num-partitionsinteger3Partition 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-partitionsinteger-1Partition 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-configsbooleantrueBring 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.

KeyTypeDefaultDescription
watermark.max-out-of-orderness-msinteger10000Allowed out-of-orderness before an event is considered late.
watermark.idleness-timeout-msinteger300000 (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-msinteger200Periodic 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.

KeyTypeDefaultDescription
processing.mdc-enabledbooleanfalsePopulate logging MDC keys (laneId, sourceTopic, sourcePartition, sourceOffset) during processing.
processing.max-task-idle-msinteger0Wait 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-partitioninteger2000Records 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-msinteger20Consumer poll() fallback timeout.
processing.dispatch-modeenum AUTO | DIRECT | LANELANERecord dispatch mode. LANE routes via key-affinity lanes; AUTO may pick DIRECT for simple stateless topologies; DIRECT is experimental.
processing.parallel-deser-modeenum AUTO | ON | OFFAUTOParallel deserialization. AUTO enables it when per-record cost is high enough; ON/OFF force it.
processing.timestamp-coordination-modeenum AUTO | ENABLED | DISABLEDAUTOEvent-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-checkenum off | warn | errorwarnVerdict 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-guardenum off | warn | errorerrorVerdict 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-msinteger10Cadence for wall-clock punctuators and scheduled sources.
processing.state-transition-history-sizeinteger20Number of past app-state transitions retained for the /state endpoint.

stoatflow.validation

Startup and single-instance safety checks. See How StoatFlow differs from KS.

KeyTypeDefaultDescription
validation.ensure-explicit-namingbooleantrueFail startup if any topology operation uses an auto-generated name (KIP-1111).
validation.validate-complete-assignmentbooleantrueWait for and verify that all source-topic partitions are assigned at startup.
validation.partition-assignment-timeout-msinteger60000Timeout to wait for complete partition assignment after subscribe.
validation.pre-flight-consumer-group-checkbooleanfalseCheck for active consumer-group members before starting (prevents accidental duplicate instances).

stoatflow.shutdown

Graceful shutdown behavior. See Probes.

KeyTypeDefaultDescription
shutdown.timeout-msinteger25000Timeout for graceful shutdown; lets in-flight barriers complete.
shutdown.leave-group-on-closebooleanfalseWhether 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-hangbooleantrueIf graceful shutdown stalls past its budget, force a clean process exit so Kubernetes restarts the pod.
shutdown.hard-exit-budget-msinteger2 × shutdown.timeout-msBudget 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.

KeyTypeDefaultDescription
commit-stall.threshold-msinteger45000If 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-msinteger5000How often the stall check runs.
commit-stall.dump-enabledbooleantrueEmit a thread dump to logs when a stall is detected.

stoatflow.rocks-db

RocksDB preset and backend. See State stores.

KeyTypeDefaultDescription
rocks-db.presetenum DEFAULT | LOW_MEMORY | HIGH_PERFORMANCEDEFAULTMemory preset. DEFAULT ≈ 256 MiB, LOW_MEMORY ≈ 64 MiB, HIGH_PERFORMANCE ≈ 1 GiB.
rocks-db.backendenum AUTO | FFM | JNIAUTORocksDB 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.

KeyTypeDefaultDescription
caching.max-entriesinteger2147483647 (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-bytesinteger (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.

KeyTypeDefaultDescription
timer-backend.typeenum HEAP | PERSISTENT | HYBRIDHYBRIDHEAP = in-memory (fast, unbounded), PERSISTENT = RocksDB only (memory-bounded), HYBRID = heap cache + RocksDB.
timer-backend.changelog-enabledbooleantrueLog timer mutations to a Kafka changelog for durability (applies to all types).
timer-backend.cache-horizon-msinteger60000HYBRID only: how far ahead timers are cached in the heap. Must be > load-interval-ms.
timer-backend.load-interval-msinteger10000HYBRID only: how often the loader pulls upcoming timers from RocksDB. Must be < cache-horizon-ms.

stoatflow.ktable / stoatflow.fk-join / stoatflow.dsl / stoatflow.topology

KeyTypeDefaultDescription
ktable.auto-reuse-source-topicsbooleantrueReuse compacted KTable source topics for restoration instead of separate changelog topics.
fk-join.use-reverse-index-cachebooleantrueMaintain an in-memory reverse index for O(1) FK-join right-side lookups (disable to save heap on very large FK tables).
dsl.store-formatenum DEFAULT | HEADERSDEFAULTGlobal 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 | errorper rulePer-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-splitenum lazy | eagerlazyWhere 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-affinityenum off | presumedoffWhether 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.

KeyTypeDefaultDescription
kafka.consumermap<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.producermap<string,string>{}Additional producer properties (e.g. compression.type, linger.ms).
kafka.restoration-consumermap<string,string>{}Overrides for the restoration consumer only.
kafka.adminmap<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.

KeyTypeDefaultDescription
license.keystring—License key string. Use ${STOATFLOW_LICENSE_KEY} interpolation; do not hard-code.
license.filestring—Path to a chmod 600 file holding the key on one line. Mutually exclusive with key (key wins).
license.environmentstring—Deployment environment label feeding the machine fingerprint. Required for the Production tier.
license.cache-dirstring~/.stoatflow/license-cache/Offline-cache directory. In Kubernetes, mount on a persistent volume so it survives restarts.
license.verbose-bannerbooleanfalseInclude the customer name in the startup banner.
License env vars and -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.

KeyTypeDefaultDescription
ha.modeenumoffoff (single instance) or active-standby (hot-standby cluster: one active + one or more warm standbys).
ha.pod-idstringPOD_NAME → HOSTNAME → hostnameStable per-pod identity distinguishing the peers.
ha.coordination-topicstring__stoatflow_<applicationId>_haSingle-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-standbysinteger1Caught-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-standbysinteger3Ceiling on standbys, bounding changelog fan-out (each standby is one more changelog reader). Total instances ≤ max-standbys + 1.
ha.failover-priorityinteger0Per-pod tie-break hint ordering candidates within the equal-lag bucket (higher wins). Never overrides lag or the hash floor.
ha.promotion-settle-window-msinteger500After 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-msinteger1000How often each pod publishes its liveness and role.
ha.staleness-threshold-msinteger5000Gap without a peer heartbeat before it's considered stale. Must be ≥ ha.heartbeat-ms.
ha.staleness-missesinteger3Consecutive missed checks before a standby is elected to promote (debounce).
ha.acceptable-recovery-laginteger50000Total 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-msinteger5000Grace 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-msinteger60000Progress-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.

KeyTypeDefaultDescription
runtime.http.enabledbooleantrueEnable the HTTP server (health, metrics, info, topology, control endpoints).
runtime.http.portinteger8080Port to bind.
runtime.http.hoststring0.0.0.0Bind address.
runtime.http.backloginteger50Max pending connections in the socket backlog.
runtime.http.debug.enabledbooleantrueRegister the /debug/threads and /debug/barriers diagnostic endpoints. Disable to reduce attack surface in hardened deployments.

runtime.metrics

Micrometer / Prometheus metrics. See Metrics.

KeyTypeDefaultDescription
runtime.metrics.enabledbooleantrueEnable metrics collection and the /metrics Prometheus endpoint.
runtime.metrics.prefixstringstoatflowPrefix for all metric names.
runtime.metrics.recording-levelenum info | debug | traceinfoMetric detail level. info is production-safe; higher levels add cardinality/overhead.
runtime.metrics.common-tagsmap<string,string>{}Tags applied to all metrics (e.g. env, region).
runtime.metrics.bind-jvm-metricsbooleantrueBind JVM metrics (memory, GC, threads).
runtime.metrics.namingenum stoatflow | kafka-streams | bothstoatflowMetric-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.shapeenum micrometer-binder | jmx-exportermicrometer-binderPrometheus shape for the KS-named series. Read only when naming ≠ stoatflow.
runtime.metrics.ks-compat.sample-window-msinteger30000Trailing window for KS -rate/-avg/-max twins (min 5000).
runtime.metrics.ks-compat.kafka-versionstring4.1.0Synthetic kafka_version tag value (micrometer-binder shape).
runtime.metrics.ks-compat.thread-idstring(auto)Override for the synthetic KS thread-id (default <application-id>-StreamThread-1).
runtime.metrics.ks-compat.client-idstring(auto)Override for the synthetic KS client-id.

runtime.endpoints

Visibility of the /info and /config endpoints. See REST API.

KeyTypeDefaultDescription
runtime.endpoints.config.enabledbooleantrueEnable the /config endpoint (merged config with sensitive values masked).
runtime.endpoints.info.show-appbooleantrueInclude application info (applicationId) in /info.
runtime.endpoints.info.show-javabooleantrueInclude Java runtime info in /info.
runtime.endpoints.info.show-kafkabooleantrueInclude Kafka config (bootstrap servers) in /info.
runtime.endpoints.info.show-uptimebooleantrueInclude uptime in /info.
runtime.endpoints.info.show-statebooleantrueInclude state + transition history in /info.
runtime.endpoints.info.show-watermarksbooleantrueInclude watermark info in /info.
runtime.endpoints.info.show-endpointsbooleantrueInclude the list of available endpoints in /info.
runtime.endpoints.info.show-offsetsbooleantrueInclude the consumer lag summary (totalLag, per-topic lags) in /info.
runtime.endpoints.info.show-consumerbooleantrueInclude the consumer group summary (groupId, subscription, partitions) in /info.

runtime.health

Health-check timeouts. See Health checks.

KeyTypeDefaultDescription
runtime.health.kafka-broker.timeout-msinteger2000Timeout for the Kafka broker connectivity check.
runtime.health.schema-registry.timeout-msinteger5000Timeout for the Schema Registry check (auto-enabled when schema-registry-url is set).

logging

Per-package log levels applied at startup (overrides logback.xml).

KeyTypeDefaultDescription
logging.levelmap<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}