Kafka Streams compatibility matrix
The engineering view of Kafka Streams DSL parity in StoatFlow: every operator, helper class, and store method, with its current status and notes where behaviour differs. Targets Kafka Streams 4.3.x: StoatFlow reimplements the Kafka Streams DSL surface under its own package, so most KS applications port by swapping the dependency and rewriting imports โ see Migrating from Kafka Streams, and let the automated port recipe do it for you.
For the high-level, feature-by-feature comparison against Kafka Streams and Flink, see Comparison matrix. For how-to guidance on the operators below, see Building.
1.0.0-beta.8). A small number of rows differ in signature from Kafka Streams (marked โ ๏ธ) or are intentionally absent (marked ๐) โ read the notes.Legend
| Symbol | Meaning |
|---|---|
| โ | Implemented |
| โ ๏ธ | Partial โ stub, limited, or differing signature |
| โ | Not implemented yet |
| ๐ | Incompatible, or won't be implemented (not needed in the single-instance model) |
| โ๏ธ | Deprecated in Kafka Streams |
| ๐ | StoatFlow extension โ not in Kafka Streams |
| ๐ | StoatFlow extension on an otherwise KS-compatible interface |
| ๐ก | Recognised no-op โ accepted and warned about, never an error (configuration keys only) |
| ๐งฉ | Client pass-through โ forwarded to the Kafka clients the key is valid for (configuration keys only) |
StreamsBuilder
| Method | Status | Notes |
|---|---|---|
| StreamsBuilder() | โ | No-arg |
| StreamsBuilder(TopologyConfig) | โ | KSC-77 โ seeds build-time defaults (serdes, DSL store format) and the KIP-1112 ProcessorWrapper from a TopologyConfig projection of StreamsConfig. Named-topology / per-topology overrides N/A (single-instance) |
| stream(topic, consumed?) | โ | Single topic subscription; single-arg form Java-callable |
| stream(Collection<String>) | โ | Multiple topics subscription |
| stream(Pattern) | โ | Pattern-based subscription |
| table(topic, consumed?) | โ | |
| table(topic, materialized) | โ | Explicit skip-Consumed overload (KS parity) |
| table(topic, consumed?, materialized) | โ | With state store |
| globalTable(topic, consumed?) | โ | Wraps KTable |
| globalTable(topic, materialized) | โ | Without Consumed parameter |
| build() | โ | Returns Topology |
| build(Properties) | โ ๏ธ | The Properties are ignored โ logs a warning and delegates to build(). Config carries over via StreamsConfig.fromProperties(...) / the KS-shaped StreamsConfig(Properties) ctor instead (ADR-124) |
| addStateStore(storeBuilder) | โ | Register standalone store |
| addStateStore(storeBuilder, keySerde, valueSerde) | โ | With changelog Serdes |
| addStateStore(storeSupplier, keySerde, valueSerde) | โ | Convenience overload |
| addGlobalStore(storeBuilder, topic, consumed, processor) | โ | Custom update processor |
| addGlobalStore(storeBuilder, topic, consumed) | โ | Default put/delete semantics |
| addReadOnlyStateStore(storeBuilder, topic, consumed, processor) | โ | KIP-813: custom processor |
| addReadOnlyStateStore(storeBuilder, topic, consumed) | โ | KIP-813: default put/delete semantics |
| scheduled(interval, type, emitter) | ๐ | Interval-based periodic record source |
| scheduled(interval, type, named, emitter) | ๐ | With explicit naming |
| scheduled(interval, type, keySerde, emitter) | ๐ | With explicit key serde for lane assignment |
| scheduled(cron, emitter) | ๐ | Cron-based calendar scheduling |
| scheduled(cron, named, emitter) | ๐ | Cron with explicit naming |
| scheduled(cron, keySerde, emitter) | ๐ | Cron with explicit key serde for lane assignment |
Topology (Processor API)
Both the high-level DSL (StreamsBuilder) and the low-level Topology builder are supported.
| Method | Status | Notes |
|---|---|---|
| describe() | โ | Returns the Kafka Streams TopologyDescription (nested Source/Processor/Sink/Subtopology/GlobalStore node graph). Breaking vs older StoatFlow: previously returned the native model โ that view moved to describeStoatFlow() |
| describeStoatFlow() | ๐ | StoatFlow-native topology model (the former describe() shape) |
| addSource(name, topics...) | โ | 14 overloads: offset reset, timestamp extractor, deserializers, pattern |
| addProcessor(name, supplier, parents...) | โ | User-defined processor with ProcessorSupplier |
| addSink(name, topic, parents...) | โ | 8 overloads: serializers, partitioner, TopicNameExtractor |
| addStateStore(builder, processors...) | โ | Register store and optionally connect to processors |
| addGlobalStore(...) | โ | 2 variants: with/without TimestampExtractor |
| addReadOnlyStateStore(...) | โ | 2 variants (KIP-813): source topic is changelog |
| connectProcessorAndStateStores(...) | โ | Connect existing store to processor |
TopologyDescription
describe() returns the Kafka Streams TopologyDescription โ a graph of nested node interfaces.
| Member | Status | Notes |
|---|---|---|
| subtopologies() / globalStores() | โ | Sets of Subtopology / GlobalStore |
| Subtopology.id() / nodes() | โ | |
| Node.name() / predecessors() / successors() | โ | |
| Source.topicSet() / topicPattern() | โ | |
| Processor.stores() | โ | Connected store names |
| Sink.topic() / topicNameExtractor() | โ | Static or dynamic routing |
| GlobalStore.id() / source() / processor() | โ |
KStream โ stateless transformations
| Method | Status | Notes |
|---|---|---|
| filter(predicate) | โ | |
| filterNot(predicate) | โ | |
| map(mapper) | โ | Key-changing; the sub-topology boundary opens downstream, where an operator needs the new key's affinity (KS repartitionRequired model, ADR-138) |
| mapValues(ValueMapper) | โ | |
| mapValues(ValueMapperWithKey) | โ | Key-aware value mapper variant |
| flatMap(mapper) | โ | Key-changing; the sub-topology boundary opens downstream, where an operator needs the new key's affinity (KS repartitionRequired model, ADR-138) |
| flatMapValues(ValueMapper) | โ | |
| flatMapValues(ValueMapperWithKey) | โ | Key-aware value mapper variant |
| selectKey(keySelector) | โ | Key-changing; the sub-topology boundary opens downstream, where an operator needs the new key's affinity (KS repartitionRequired model, ADR-138) |
| peek(action) | โ | Java BiConsumer overload |
| merge(other) | โ | |
| split() | โ | Returns BranchedKStream |
| repartition() | โ | In-memory only |
| repartition(Repartitioned) | โ | Custom partitioner, naming |
| print() | โ | Configurable via Printed |
KStream โ terminal operations
| Method | Status | Notes |
|---|---|---|
| forEach(action) | โ | Java BiConsumer overload |
| to(topic, produced?) | โ | Static topic routing |
| to(TopicNameExtractor, produced?) | โ | Dynamic topic routing |
| toTable() | โ | Auto-generates store name |
| toTable(named?, materialized?) | โ | With naming and materialization |
KStream โ Processor API
| Method | Status | Notes |
|---|---|---|
| process(processorSupplier, named?, storeNames...) | โ | Key-changing; ProcessorSupplier. Connects the named stores (and any declared via supplier.stores()). Like KS, it does not open a sub-topology boundary of its own โ see the note below |
| processValues(processorSupplier, named?, storeNames...) | โ | Non-key-changing; FixedKeyProcessorSupplier + named/declared stores. Like KS, it does not open a sub-topology boundary of its own โ see the note below |
| transform() | ๐ | Removed from KStream in KS 4.x; use process() |
| transformValues() | ๐ | Removed from KStream in KS 4.x; use processValues() |
process() / processValues(), even with connected stores, and since 1.0.0 neither does StoatFlow (topology.processor-api-key-affinity: off โ presumed restores the pre-1.0.0 boundary). After a many-to-one re-key that means per-key state at such a node is fragmented across lanes, exactly as it is across tasks in KS. StoatFlow adds a guard KS does not have: it refuses to compile the provable case โ a proven re-key into a node with a writable store (topology.validation.papi-key-affinity-presumed, default error) โ and warns where the feeder is itself a Processor API node, whose key change is only a conservative default (papi-key-affinity-presumed-chain, default warn). See Key affinity after a re-key.KStream โ grouping
| Method | Status | Notes |
|---|---|---|
| groupByKey() | โ | Returns KGroupedStream |
| groupByKey(Grouped) | โ | With Serdes configuration |
| groupBy(keySelector) | โ | Key-changing; the sub-topology boundary opens downstream, where an operator needs the new key's affinity (KS repartitionRequired model, ADR-138) |
| groupBy(keySelector, Grouped) | โ | With Serdes configuration |
KStream โ joins
| Method | Status | Notes |
|---|---|---|
| join(KStream, ValueJoiner, windows) | โ | Stream-stream windowed join |
| join(KStream, ValueJoiner, windows, streamJoined) | โ | With StreamJoined config |
| join(KStream, ValueJoinerWithKey, windows) | โ | Key-aware joiner variant |
| join(KStream, ValueJoinerWithKey, windows, streamJoined) | โ | Key-aware joiner with config |
| leftJoin(KStream, ValueJoiner, windows) | โ | Stream-stream left join |
| leftJoin(KStream, ValueJoiner, windows, streamJoined) | โ | With StreamJoined config |
| leftJoin(KStream, ValueJoinerWithKey, windows) | โ | Key-aware joiner variant |
| leftJoin(KStream, ValueJoinerWithKey, windows, streamJoined) | โ | Key-aware joiner with config |
| outerJoin(KStream, ValueJoiner, windows) | โ | Stream-stream outer join |
| outerJoin(KStream, ValueJoiner, windows, streamJoined) | โ | With StreamJoined config |
| outerJoin(KStream, ValueJoinerWithKey, windows) | โ | Key-aware joiner variant |
| outerJoin(KStream, ValueJoinerWithKey, windows, streamJoined) | โ | Key-aware joiner with config |
| join(KTable, ValueJoiner) | โ | Stream-table inner join |
| join(KTable, ValueJoinerWithKey) | โ | Key-aware joiner variant |
| leftJoin(KTable, ValueJoiner) | โ | Stream-table left join |
| leftJoin(KTable, ValueJoinerWithKey) | โ | Key-aware joiner variant |
| join(GlobalKTable, keySelector, ValueJoiner) | โ | Stream-GlobalKTable inner join |
| join(GlobalKTable, keySelector, ValueJoinerWithKey) | โ | Key-aware joiner variant |
| join(GlobalKTable, keySelector, ValueJoiner|ValueJoinerWithKey, Named) | โ | Named overloads |
| leftJoin(GlobalKTable, keySelector, ValueJoiner) | โ | Stream-GlobalKTable left join |
| leftJoin(GlobalKTable, keySelector, ValueJoinerWithKey) | โ | Key-aware joiner variant |
| leftJoin(GlobalKTable, keySelector, ValueJoiner|ValueJoinerWithKey, Named) | โ | Named overloads |
KStream โ branching (BranchedKStream)
| Method | Status | Notes |
|---|---|---|
| branch(predicate) | โ | |
| branch(predicate, branched) | โ | With Branched config |
| defaultBranch() | โ | |
| defaultBranch(branched) | โ | With Branched config |
| noDefaultBranch() | โ |
KGroupedStream
| Method | Status | Notes |
|---|---|---|
| count() | โ | With Materialized overloads |
| reduce(reducer) | โ | With Materialized overloads |
| aggregate(initializer, aggregator) | โ | With Materialized overloads |
| windowedBy(TimeWindows) | โ | Tumbling/hopping windows โ TimeWindowedKStream |
| windowedBy(SlidingWindows) | โ | Sliding windows (KIP-450) โ returns TimeWindowedKStream, exactly like Kafka Streams (no separate SlidingWindowedKStream type) |
| windowedBy(SessionWindows) | โ | Session windows |
| cogroup() | โ | Returns CogroupedKStream |
Windowed grouped streams
TimeWindowedKStream
Reached by both windowedBy(TimeWindows) (tumbling/hopping) and windowedBy(SlidingWindows) (KIP-450
sliding) โ sliding windows flow through this type, exactly as in Kafka Streams. All overloads take a
plain-key Materialized<K, V, โฆ>.
| Method | Status | Notes |
|---|---|---|
| count() | โ | With Materialized overloads (plain key) |
| reduce(reducer) | โ | With Materialized overloads |
| aggregate(initializer, aggregator) | โ | With Materialized overloads |
| emitStrategy() | โ | OnWindowUpdate (default), OnWindowClose |
SessionWindowedKStream
| Method | Status | Notes |
|---|---|---|
| count() | โ | With session merger |
| reduce(reducer) | โ | Reducer doubles as merger |
| aggregate(initializer, aggregator, sessionMerger) | โ | With session merger |
| emitStrategy() | โ | OnWindowUpdate (default), OnWindowClose |
Cogrouped streams
CogroupedKStream
| Method | Status | Notes |
|---|---|---|
| cogroup(KGroupedStream, aggregator) | โ | Add another stream to the cogroup |
| aggregate(initializer) | โ | With Materialized overloads |
| windowedBy(TimeWindows) | โ | Returns TimeWindowedCogroupedKStream |
| windowedBy(SlidingWindows) | โ | Sliding windows (KIP-450) โ returns TimeWindowedCogroupedKStream |
| windowedBy(SessionWindows) | โ | Returns SessionWindowedCogroupedKStream |
TimeWindowedCogroupedKStream
| Method | Status | Notes |
|---|---|---|
| aggregate(initializer) | โ | With Materialized overloads |
| emitStrategy() | โ | OnWindowUpdate (default), OnWindowClose |
SessionWindowedCogroupedKStream
| Method | Status | Notes |
|---|---|---|
| aggregate(initializer, sessionMerger) | โ | With Materialized overloads; (initializer, sessionMerger) argument order matches Kafka Streams |
| emitStrategy() | โ | OnWindowUpdate (default), OnWindowClose |
KTable โ transformations
| Method | Status | Notes |
|---|---|---|
| filter(predicate) | โ | Derived table (no state store) |
| filter(predicate, Named) | โ | With naming |
| filter(predicate, Materialized) | โ | With materialization |
| filter(predicate, Named, Materialized) | โ | With naming and materialization |
| filterNot(predicate) | โ | Inverse of filter |
| filterNot(predicate, Named) | โ | With naming |
| filterNot(predicate, Materialized) | โ | With materialization |
| filterNot(predicate, Named, Materialized) | โ | With naming and materialization |
| mapValues(ValueMapper) | โ | Derived table (no state store) |
| mapValues(ValueMapper, Named) | โ | With naming |
| mapValues(ValueMapper, Materialized) | โ | With materialization |
| mapValues(ValueMapper, Named, Materialized) | โ | With naming and materialization |
| mapValues(ValueMapperWithKey) | โ | Key-aware value mapper variant |
| mapValues(ValueMapperWithKey, Named) | โ | With naming |
| mapValues(ValueMapperWithKey, Materialized) | โ | With materialization |
| mapValues(ValueMapperWithKey, Named, Materialized) | โ | With naming and materialization |
| toStream() | โ | Same key type |
| toStream(KeyValueMapper) | โ | Key-changing variant; the sub-topology boundary opens downstream, where an operator needs the new key's affinity (ADR-138) |
| queryableStoreName() | โ | Returns store name if materialized |
| groupBy(keyValueSelector) | โ | Selector returns KeyValue<KR, VR>; returns KGroupedTable<KR, VR> |
| groupBy(keyValueSelector, Grouped) | โ | With Serdes configuration |
| suppress(Suppressed) | โ | untilWindowCloses, untilTimeLimit |
| transformValues(ValueTransformerWithKeySupplier, named?, storeNames...) | โ | Stateful per-key value transform; returns KTable<K, VR> |
| transformValues(ValueTransformerWithKeySupplier, Materialized, Named, storeNames...) | โ | Materializes the result into a queryable store (tombstone-aware: a null transform deletes). Supplier parameter carries Kafka Streams' ? super K, ? super V, ? extends VR wildcards, so a supplier producing a subtype of the result value type still fits |
KTable โ primary-key joins
Both tables share key type K. No co-partitioning required (global state model).
| Method | Status | Notes |
|---|---|---|
| join(KTable, joiner) | โ | Inner join; both must be materialized |
| join(KTable, joiner, Materialized) | โ | With materialization of join result |
| join(KTable, joiner, Named, Materialized) | โ | With naming and materialization |
| leftJoin(KTable, joiner) | โ | Left table always emits |
| leftJoin(KTable, joiner, Materialized) | โ | With materialization of join result |
| leftJoin(KTable, joiner, Named, Materialized) | โ | With naming and materialization |
| outerJoin(KTable, joiner) | โ | Full outer join |
| outerJoin(KTable, joiner, Materialized) | โ | With materialization of join result |
| outerJoin(KTable, joiner, Named, Materialized) | โ | With naming and materialization |
KTable โ foreign-key joins
Foreign-key extractor: a Java Function<V, KO?> (or BiFunction<K, V, KO?> for key-aware extraction) โ or a Kotlin (V) -> KO? / (K, V) -> KO? lambda.
| Method | Status | Notes |
|---|---|---|
| join(KTable, fkExtractor, joiner) | โ | Inner FK join |
| join(KTable, fkExtractor, joiner, Materialized) | โ | With materialization of join result |
| join(KTable, fkExtractor, joiner, TableJoined, Materialized) | โ | With naming and materialization |
| leftJoin(KTable, fkExtractor, joiner) | โ | Left FK join |
| leftJoin(KTable, fkExtractor, joiner, Materialized) | โ | With materialization of join result |
| leftJoin(KTable, fkExtractor, joiner, TableJoined, Materialized) | โ | With naming and materialization |
| outerJoin(KTable, fkExtractor, joiner) | โ ๏ธ | Not supported in KS either |
KGroupedTable
KGroupedTable is the result of KTable.groupBy() and uses changelog semantics โ moving a key from group A to group B decrements A and increments B. The source KTable must be materialized for groupBy() to track old values.
| Method | Status | Notes |
|---|---|---|
| count() | โ | Changelog semantics |
| reduce(adder, subtractor) | โ | Kafka Streams compatible; both adder and subtractor are Reducer<V> |
| aggregate(init, adder, subtractor) | โ | Kafka Streams compatible; both adder and subtractor are Aggregator<K, V, VR> โ no StoatFlow-only Subtractor type |
GlobalKTable
| Method | Status | Notes |
|---|---|---|
| queryableStoreName() | โ | Returns underlying table's store name |
| asKTable() | ๐ | StoatFlow extension to unwrap |
Processor API
ProcessorContext
| Method | Status | Notes |
|---|---|---|
| forward(record) | โ | Forward to downstream processors |
| forward(record, childName) | โ | Forward to specific child |
| getStateStore(name) | โ | Access connected state stores โ declared via ProcessorSupplier.stores() or the process(supplier, "name") varargs. An undeclared store throws StoreNotConnectedException (KS parity). Global stores (addGlobalStore, globalTable) are exempt and returned read-only unless the node declares them, in which case they are writable (a StoatFlow extension โ KS makes declaring a global store a TopologyException). The read-only view covers every store family (plain, timestamped, versioned, headers-aware) and keeps the store's declared type, so the caller's getStateStore<TimestampedKeyValueStore<..>> cast still succeeds; read-only stores (addReadOnlyStateStore) are exempt too โ a deliberate divergence, KS exempts only globals |
| schedule(interval, type, punctuator) | โ | KS-compatible punctuator |
| schedule(interval, type, mode, punctuator) | ๐ | StoatFlow extension with BLOCKING/NON_BLOCKING mode |
| schedule(interval, startTime, type, punctuator) | โ | KIP-1146: anchored punctuation with grid-aligned fire times |
| schedule(interval, startTime, type, mode, punctuator) | โ | KIP-1146: anchored punctuation with BLOCKING/NON_BLOCKING mode |
| recordMetadata() | โ | Source partition, offset, topic |
| currentSystemTimeMs() | โ | Wall-clock time via System.currentTimeMillis() |
| currentStreamTimeMs() | โ | Delegates to currentWatermarkMs() (global watermark) |
| currentWatermarkMs() | ๐ | Global watermark in epoch millis |
| timerService() | ๐ | Key-based timer access (Flink-inspired) |
Processor interfaces
| Type | Status | Notes |
|---|---|---|
| Processor<KIn,VIn,KOut,VOut> | โ | Key-changing processor interface |
| FixedKeyProcessor<KIn,VIn,VOut> | โ | Key-preserving processor interface |
| ContextualProcessor<KIn,VIn,KOut,VOut> | โ | Abstract base managing ProcessorContext |
| ContextualFixedKeyProcessor<KIn,VIn,VOut> | โ | Abstract base managing FixedKeyProcessorContext |
| ProcessorSupplier<KIn,VIn,KOut,VOut> | โ | Factory for Processor instances |
| ProcessorSupplier.stores() | โ | KS 4.x pattern for declaring stores inline |
| FixedKeyProcessorSupplier<KIn,VIn,VOut> | โ | Factory for FixedKeyProcessor instances |
| ValueTransformerWithKey<K,V,VR> | โ | Consumed by KTable.transformValues; init binds StoatFlow's FixedKeyProcessorContext |
| ValueTransformerWithKeySupplier<K,V,VR> | โ | Factory for ValueTransformerWithKey; declares stores via stores(). As a KTable.transformValues parameter it carries Kafka Streams' ? super K, ? super V, ? extends VR wildcards |
| ValueTransformer / Transformer (+ suppliers) | โ๏ธ | Deprecated in KS and removed from KStream; kept as nameable types for source compatibility |
init() / close() lifecycle | โ | Once per task โ init() before any records, close() once at shutdown (KS contract), across all processor paths incl. processValues / transformValues |
FixedKeyProcessor.onTimer + schedule() / timerService() | โ | Full Processor timer parity for FixedKeyProcessor (processValues): periodic punctuators via schedule() and keyed timers via timerService() โ onTimer(...) (key-preserving FixedKeyTimerContext, full state access, re-registration). Record timestamp is the event time |
Punctuators and timers
| Type / Method | Status | Notes |
|---|---|---|
| PunctuationType.STREAM_TIME | โ | Fires on event-time watermark advancement |
| PunctuationType.WALL_CLOCK_TIME | โ | Fires on system clock |
| PunctuatorMode.BLOCKING | ๐ | Barriers wait for punctuator completion before commit |
| PunctuatorMode.NON_BLOCKING | ๐ | Barriers proceed independently (default) |
| Punctuator.punctuate(timestamp) | โ | Callback invoked at scheduled time |
| Cancellable.cancel() | โ | Cancel scheduled punctuator or timer |
| TimerService.registerEventTimeTimer(key, timestamp) | ๐ | Fire when watermark โฅ timestamp |
| TimerService.registerProcessingTimeTimer(key, timestamp) | ๐ | Fire at wall-clock time |
| TimerService.deleteEventTimeTimer(key, timestamp) | ๐ | Cancel event-time timer |
| TimerService.deleteProcessingTimeTimer(key, timestamp) | ๐ | Cancel processing-time timer |
| TimerService.currentWatermark() | ๐ | Current event-time watermark |
| TimerService.currentProcessingTime() | ๐ | Current wall-clock time |
PunctuatorMode and the key-based TimerService (which fires in the key's affinity lane, also with full read/write state access) are StoatFlow extensions.Scheduled sources (StoatFlow extension)
A ScheduledEmitter โ registered via StreamsBuilder.scheduled(...) โ periodically emits records (interval- or cron-based) using the ScheduledEmitterContext below, without consuming from Kafka.
ScheduledEmitterContext
| Method | Status | Notes |
|---|---|---|
| forward(key, value) | ๐ | Emit record with current wall-clock time |
| forward(key, value, timestamp) | ๐ | Emit record with explicit timestamp |
| forward(record) | ๐ | Emit record with key, value, timestamp, headers |
| getStateStore(name) | ๐ | Access a state store read-only โ writes throw UnsupportedOperationException. Every family is covered (plain, timestamped, versioned, headers-aware) and the view keeps the store's declared type, so a getStateStore<TimestampedKeyValueStore<..>> cast still succeeds |
| currentWatermarkMs() | ๐ | Current watermark (global event-time progress) |
| currentStreamTimeMs() | โ | Delegates to currentWatermarkMs() (KS compat) |
| currentSystemTimeMs() | โ | Wall-clock time via System.currentTimeMillis() (KS compat) |
| currentWallClockTime() | ๐ | Current wall-clock time |
CronExpression
| Method | Status | Notes |
|---|---|---|
| unix(expression) | ๐ | Unix cron (5 fields: min hour dom mon dow) |
| quartz(expression) | ๐ | Quartz cron (6 fields with seconds) |
| spring(expression) | ๐ | Spring cron (6 fields with seconds) |
| parse(expression, cronType) | ๐ | Parse with explicit type |
| nextExecutionTime(afterMs, zoneId?) | ๐ | Calculate next fire time |
| timeUntilNextExecution(fromMs, zoneId?) | ๐ | Duration until next fire |
Functional interfaces
KS-compatible functional interfaces backing the Java-friendly DSL overloads.
| Interface | Status | Used in |
|---|---|---|
KeyValueMapper<K, V, VR> | โ | map, flatMap, groupBy, FK joins |
ValueMapper<V, VR> | โ | KTable.mapValues, FK join extractors |
ValueMapperWithKey<K, V, VR> | โ | mapValues with key access |
Predicate<K, V> | โ | filter, filterNot |
ForeachAction<K, V> | โ | peek, forEach |
Initializer<VA> | โ | All aggregate() |
Aggregator<K, V, VA> | โ | All aggregate() โ incl. both the adder and subtractor of KGroupedTable.aggregate() (KS uses Aggregator for both; no Subtractor type) |
Reducer<V> | โ | All reduce() |
Merger<K, V> | โ | Session window aggregate() |
ValueJoiner<V1, V2, VR> | โ | All joins |
ValueJoinerWithKey<K, V1, V2, VR> | โ | Key-aware joins |
StreamPartitioner<K, V> | โ | KIP-837 partitions(...): Optional<Set<Integer>> (sole method). Sink multicast matches KS; the in-memory repartition path throws on multicast (single-instance). Lives in the topology package |
Configuration helper classes
Consumed
| Method | Status | Notes |
|---|---|---|
| with(keySerde, valueSerde) | โ | |
| with(keySerde, valueSerde, timestampExtractor, resetPolicy) | โ | Extractor adapted to a stream-time watermark strategy |
| keySerde(serde) | โ | |
| valueSerde(serde) | โ | |
| as(name) | โ | |
| withWatermarkStrategy(strategy) | ๐ | StoatFlow extension (mutually exclusive with withTimestampExtractor) |
| withTimestampExtractor() | โ | KS TimestampExtractor adapted to a per-source watermark strategy that assigns event time + emits stream-time watermarks (windowing/suppress close as in KS); use withWatermarkStrategy for bounded-out-of-orderness/idleness |
| withOffsetResetPolicy() | โ | Per-source offset reset. AutoOffsetReset: Earliest / Latest / None / byDuration(Duration) (KIP-1106 โ seek to the first offset at/after now โ duration) |
| withNumberOfLanes(int) | ๐ | Sets this source chain's lane count (parallelism) |
| withLaneQueueCapacity(int) | ๐ | Sets this source chain's per-lane queue capacity |
WatermarkStrategy (Flink-inspired)
Event-time progress is driven by a WatermarkStrategy. A KS Consumed.withTimestampExtractor(...) is supported and adapted to one under the hood; Consumed.withWatermarkStrategy(...) is the richer StoatFlow-native form (bounded out-of-orderness, idleness, alignment).
| Method | Status | Notes |
|---|---|---|
| forBoundedOutOfOrderness(maxOutOfOrderness) | ๐ | Bounded out-of-orderness |
| forMonotonousTimestamps() | ๐ | Strictly increasing timestamps |
| noWatermarks() | ๐ | Processing-time only |
| withTimestampAssigner(assigner) | ๐ | Custom event-time assignment |
| withIdleness(duration) | ๐ | Idle-source handling |
| withWatermarkAlignment(group, maxDrift, updateInterval) | ๐ | Flink FLIP-217 alignment; pauses a partition whose watermark drifts beyond the group cap |
The TimestampExtractor SAM is present with the KS-exact, non-generic signature long extract(ConsumerRecord<Object, Object>, long), and Consumed.withTimestampExtractor(...) wires it to event-time + stream-time watermarks.
KS-optional builder arguments (serdes, grace, partitioner, offset-reset, name) across Consumed/Grouped/Produced/Repartitioned/Joined/StreamJoined/Materialized/Branched accept null (= "use the default"), matching Kafka Streams; Named itself still requires a non-null name.
Grouped
| Method | Status | Notes |
|---|---|---|
| as(name) | โ | |
| keySerde(serde) | โ | |
| valueSerde(serde) | โ | |
| with(keySerde, valueSerde) | โ | |
| with(name, keySerde, valueSerde) | โ | |
| withName(name) | โ | |
| withKeySerde(serde) | โ | |
| withValueSerde(serde) | โ | |
| withNumberOfLanes(int) | ๐ | Sets the grouped sub-topology's lane count |
| withLaneQueueCapacity(int) | ๐ | Sets the grouped sub-topology's per-lane queue capacity |
Produced
| Method | Status | Notes |
|---|---|---|
| with(keySerde, valueSerde) | โ | |
| keySerde(serde) | โ | |
| valueSerde(serde) | โ | |
| as(name) | โ | |
| streamPartitioner(partitioner) | โ |
Materialized
| Method | Status | Notes |
|---|---|---|
| as(storeName) | โ | |
| as(storeSupplier) | โ | |
| as(DslStoreSuppliers) | โ | Store type supplier |
| with(keySerde, valueSerde) | โ | |
| keySerde(serde) | โ | |
| valueSerde(serde) | โ | |
| withCachingEnabled() | โ | Suppress downstream emissions (default enabled) |
| withCachingDisabled() | โ | Emit all intermediate updates downstream |
| withLoggingEnabled() | โ | Enable changelog with optional topic config |
| withLoggingDisabled() | โ | Disable changelog for this store |
| withRetention() | โ | Override computed retention for windowed stores |
| withStoreType(DslStoreSuppliers) | โ | Store type selection |
| withRecordHeaders() | ๐ | Persist the record's headers alongside key/value in the store (KIP-1271 / KIP-1285) |
| withoutRecordHeaders() | ๐ | Disable header persistence (default) |
Branched
| Method | Status | Notes |
|---|---|---|
| as(name) | โ | |
| withName(name) | โ | |
| withFunction(chain) | โ | |
| withConsumer(consumer) | โ | Java Consumer overload |
Named
Named is open/subclassable, so KSML's name-validation subclasses work.
| Method | Status | Notes |
|---|---|---|
| as(name) | โ | |
| withName(name) | โ | Fluent builder form |
| name() | โ | Read the configured name |
Printed
| Method | Status | Notes |
|---|---|---|
| toSysOut() | โ | Default stdout |
| toFile(filePath) | โ | File output |
| withLabel(label) | โ | Prefix label |
| withKeyValueMapper(mapper) | โ | Custom formatting; takes a KS KeyValueMapper<K, V, String> |
| withName(processorName) | โ | Set processor name |
Joined
Joined<K, VLeft, VRight> is the Kafka Streams generic form. Its serdes and grace period are advisory โ stored and round-tripped for source compatibility, but StoatFlow resolves the effective serdes/grace from the upstream Consumed/Grouped configuration.
| Method | Status | Notes |
|---|---|---|
| as(name) | โ | |
| create() | โ | Create with defaults |
| with(keySerde, valueSerde, otherValueSerde) | โ | Static factory; serdes advisory |
| withName(name) | โ | |
| withGracePeriod(duration) | โ | Advisory grace period |
| withKeySerde(serde) | โ | Advisory |
| withValueSerde(serde) | โ | Advisory |
| withOtherValueSerde(serde) | โ | Advisory |
| name() | โ | Read the configured name |
| gracePeriod() | โ | Round-trips the advisory grace period |
| keySerde() / valueSerde() / otherValueSerde() | โ | Round-trip the advisory serdes |
TableJoined
| Method | Status | Notes |
|---|---|---|
| as(name) | โ | |
| with() | โ | Defaults |
| with(partitioner, otherPartitioner) | โ | Accepted no-op stub (partitioning is N/A single-instance) |
| withName(name) | โ | |
| withPartitioner() | โ | Accepted no-op stub for KS source compatibility |
| withOtherPartitioner() | โ | Accepted no-op stub for KS source compatibility |
JoinWindows
| Method | Status | Notes |
|---|---|---|
| of(timeDifference) | โ๏ธ | Deprecated; use ofTimeDifferenceWithNoGrace |
| ofTimeDifferenceWithNoGrace(timeDifference) | โ | Symmetric window, no grace |
| ofTimeDifferenceAndGrace(timeDifference, grace) | โ | Symmetric window with grace |
| before(timeDifference) | โ | Asymmetric window (look back) |
| after(timeDifference) | โ | Asymmetric window (look forward) |
| grace(duration) | โ๏ธ | Deprecated; use ofTimeDifferenceAndGrace |
| size() | โ | Window size in ms |
| gracePeriodMs() | โ | Grace period in ms |
beforeMs / afterMs are exposed as read-only properties (getBeforeMs() / getAfterMs() from Java; before / after return Duration). A KS port that reads the public jw.beforeMs field replaces it with the accessor.StreamJoined
| Method | Status | Notes |
|---|---|---|
| as(name) | โ | Create with processor name |
| with(keySerde, leftValueSerde, rightValueSerde) | โ | Create with Serdes |
| create() | โ | Create with defaults |
| withName(name) | โ | Set processor name |
| withKeySerde(serde) | โ | Set key Serde |
| withLeftValueSerde(serde) | โ | Set left stream value Serde |
| withRightValueSerde(serde) | โ | Set right stream value Serde |
| withThisStoreSupplier(storeSupplier) | โ | Custom left window store |
| withOtherStoreSupplier(storeSupplier) | โ | Custom right window store |
| withLoggingEnabled() | โ | Enable changelog logging |
| withLoggingEnabled(config) | โ | Enable changelog logging with topic config |
| withLoggingDisabled() | โ | Disable changelog logging |
| withDslStoreSuppliers(suppliers) | โ | Both join stores use same type |
| withStoreName(name) | โ | Set base store name |
Repartitioned
| Method | Status | Notes |
|---|---|---|
| as(name) | โ | |
| streamPartitioner(partitioner) | โ | numPartitions = numLanes |
| withStreamPartitioner(partitioner) | โ | |
| withName(name) | โ | |
| with(keySerde, valueSerde) | โ | keySerde used for lane assignment hashing |
| withKeySerde() | โ | Used for lane assignment hashing at sub-topology boundary |
| withValueSerde() | โ | Reserved for future use |
| withNumberOfLanes(int) | ๐ | Sets the repartition sub-topology's lane count |
| numberOfLanes(int) | ๐ | Static factory; sets the lane count |
| withLaneQueueCapacity(int) | ๐ | Sets the per-lane queue capacity |
| withNumberOfPartitions() | ๐ | KS-compat alias of withNumberOfLanes (sets the lane count) |
| numberOfPartitions(int) | ๐ | KS-compat static-factory alias of numberOfLanes |
EmitStrategy
| Method | Status | Notes |
|---|---|---|
| onWindowUpdate() | โ | Default; emit on every update |
| onWindowClose() | โ | Emit only when window closes; driven by the global event-time watermark |
OnWindowClose emission is watermark-driven: a window's final result is emitted when the global watermark passes the window end plus any configured grace period. See Event time and watermarks.Suppressed
| Method | Status | Notes |
|---|---|---|
| untilWindowCloses(bufferConfig) | โ | For windowed KTables; requires a StrictBufferConfig (KS parity); flush driven by the global event-time watermark |
| untilTimeLimit(duration, bufferConfig) | โ | Rate-limits per-key updates; flush driven by the global event-time watermark |
| withName(name) | โ | KS NamedOperation; names the suppression node |
untilWindowCloses releases a key's final value once the window closes, and untilTimeLimit releases the latest buffered value once the per-key time limit elapses in event time. See Event time and watermarks. Suppress buffers (and versioned stores, which the migration tool supports as experimental) are the two store families with state-carrying migration caveats.suppress() throws a validation exception for tables backed by versioned stores โ their temporal semantics conflict with suppression.BufferConfig
| Method | Status | Notes |
|---|---|---|
| unbounded() | โ | No limits (use with caution) |
| maxRecords(count) | โ | Bounded by record count |
| maxBytes(bytes) | โ ๏ธ | Accepted so KS code compiles, but throws at build time โ in-memory object buffers have no byte size |
| withMaxRecords(count) | โ | Fluent record bound |
| withMaxBytes(bytes) | โ ๏ธ | Same as maxBytes โ throws |
| withNoBound() | โ | Remove bounds |
| emitEarlyWhenFull() | โ | Emit instead of evict on overflow |
| shutDownWhenFull() | โ | Shut down on buffer overflow |
| withLoggingEnabled(config) / withLoggingDisabled() | โ | Changelog control for the suppress buffer |
WindowedSerdes
| Method | Status | Notes |
|---|---|---|
| timeWindowedSerdeFrom(...) | โ ๏ธ | Takes Serde<T> instead of Class<T> (no serde registry) |
| timeWindowedSerdeFrom(..., windowSize) | โ ๏ธ | Takes Serde<T> only; window size encoded in serialized bytes |
| sessionWindowedSerdeFrom(...) | โ ๏ธ | Takes Serde<T> instead of Class<T> |
| TimeWindowedSerde(inner) | โ | WindowedSerdes.TimeWindowedSerde(serde) |
| TimeWindowedSerde(inner, windowSize) | โ ๏ธ | Window size not required (encoded in serialized bytes) |
| SessionWindowedSerde(inner) | โ | WindowedSerdes.SessionWindowedSerde(serde) |
Serde<T> rather than KS's Class<T> (there is no serde registry), and omit the window-size parameter (window bounds are encoded in the serialized bytes). TimeWindowedSerde.forChangelog() is not needed โ StoatFlow uses identical encoding for client and changelog.State stores
Record types & iterators
| Type | Status | Notes |
|---|---|---|
| Windowed<K> | โ | Windowed-key wrapper (key + Window) |
| ValueAndTimestamp<V> | โ | Timestamped-store value wrapper (KIP-258) |
| VersionedRecord<V> | โ | Versioned-store record; validTo() returns Optional<Long> |
| KeyValueIterator / WindowStoreIterator | โ | Closeable iterators for range/scan queries |
State store metrics
| Metric | Status | Notes |
|---|---|---|
| num-keys (KIP-1250) | โ | Entry-count gauge for in-memory KV/window/session stores |
KeyValueStore
| Method | Status | Notes |
|---|---|---|
| get(key) | โ | |
| put(key, value) | โ | |
| putIfAbsent(key, value) | โ | |
| putAll(entries) | โ | |
| delete(key) | โ | |
| compute(key, remappingFunction) | ๐ | Atomic read-compute-write; Kotlin lambda + Java BiFunction overloads |
| merge(key, value, remappingFunction) | ๐ | Atomic merge; Kotlin lambda + Java BiFunction overloads |
| all() | โ | Read-your-writes semantics |
| range(from, to) | โ | Read-your-writes semantics. Null bounds = open-ended (KIP-763, KSC-89); from > to returns empty + WARN (KS-lenient) |
| reverseAll() | โ | Read-your-writes semantics |
| reverseRange(from, to) | โ | Read-your-writes semantics. Null bounds = open-ended (KIP-763, KSC-89); from > to returns empty + WARN (KS-lenient); (low, high) bounds order, KS-exact (KSC-90 โ was (high, low) pre-2026-07-31) |
| approximateNumEntries() | โ | |
| prefixScan() | โ | KIP-614; read-your-writes semantics |
ReadOnlyKeyValueStore
| Method | Status | Notes |
|---|---|---|
| get(key) | โ | |
| containsKey(key) | ๐ | StoatFlow extension; bloom filter optimization for RocksDB |
| all() | โ | |
| range(from, to) | โ | Null bounds = open-ended (KIP-763, KSC-89); from > to returns empty + WARN (KS-lenient) |
| reverseAll() | โ | |
| reverseRange(from, to) | โ | Null bounds = open-ended (KIP-763, KSC-89); from > to returns empty + WARN (KS-lenient); (low, high) bounds order, KS-exact (KSC-90 โ was (high, low) pre-2026-07-31) |
| approximateNumEntries() | โ | |
| prefixScan(prefix, serializer) | โ | KIP-614 |
WindowStore
Key-range bounds are compared as serialized bytes (unsigned lexicographic); null bounds are open-ended (KIP-763, KSC-91). All Instant variants delegate to the Long variants.
| Method | Status | Notes |
|---|---|---|
| put(key, value, windowStartTime) | โ | |
| fetch(key, windowStartTime) | โ | Point lookup |
| fetch(key, timeFrom, timeTo) | โ | Single key time range; Long and Instant variants |
| backwardFetch(key, timeFrom, timeTo) | โ | Reverse iteration; Long and Instant variants |
| fetch(keyFrom, keyTo, timeFrom, timeTo) | โ | Key range + time range; Long and Instant variants. Null key bounds = open-ended (KIP-763, KSC-91); keyFrom > keyTo returns empty + WARN (KS-lenient) |
| backwardFetch(keyFrom, keyTo, timeFrom, timeTo) | โ | Reverse key range; bounds stay (low, high) like fetch (KS-exact); Long and Instant variants. Null key bounds = open-ended (KIP-763, KSC-91) |
| fetchAll(timeFrom, timeTo) | โ | All keys time range; Long and Instant variants |
| backwardFetchAll(timeFrom, timeTo) | โ | Reverse all keys; Long and Instant variants |
| all() | โ | All entries |
| backwardAll() | โ | Reverse iteration |
| windowSizeMs | ๐ | StoatFlow extension for window size access |
| retentionMs | ๐ | StoatFlow extension for retention access |
| expireWindows(watermark) | ๐ | StoatFlow extension for manual expiration |
| approximateNumEntries() | ๐ | StoatFlow extension for entry count |
ReadOnlyWindowStore
| Method | Status | Notes |
|---|---|---|
| All query methods from WindowStore | โ | 16 query methods total |
| containsKey(key, windowStartTime) | ๐ | StoatFlow extension; Long and Instant variants |
SessionStore
| Method | Status | Notes |
|---|---|---|
| put(sessionKey, aggregate) | โ | Windowed key |
| remove(sessionKey) | โ | Windowed key |
| fetchSession(key, startTime, endTime) | โ | Long and Instant variants |
| findSessions(key, earliestEndTime, latestStartTime) | โ | Long and Instant variants |
| backwardFindSessions(key, earliestEndTime, latestStartTime) | โ | Long and Instant variants |
| findSessions(keyFrom, keyTo, earliestEndTime, latestStartTime) | โ | Key range; Long and Instant variants. Null key bounds = open-ended (KIP-763, KSC-91); keyFrom > keyTo returns empty + WARN (KS-lenient) |
| backwardFindSessions(keyFrom, keyTo, earliestEndTime, latestStartTime) | โ | Key range; Long and Instant variants. Null key bounds = open-ended (KIP-763, KSC-91) |
| fetch(key) | โ | All sessions for single key |
| backwardFetch(key) | โ | Reverse order |
| fetch(keyFrom, keyTo) | โ | Key range queries. Null key bounds = open-ended (KIP-763, KSC-91); keyFrom > keyTo returns empty + WARN (KS-lenient) |
| backwardFetch(keyFrom, keyTo) | โ | Reverse order; bounds stay (low, high) (KS-exact). Null key bounds = open-ended (KIP-763, KSC-91) |
| findAll() | ๐ | StoatFlow extension |
| expireSessions(watermark) | ๐ | StoatFlow extension |
| approximateNumEntries() | ๐ | StoatFlow extension |
ReadOnlySessionStore
| Method | Status | Notes |
|---|---|---|
| All query methods from SessionStore | โ | Query methods for IQ access |
| containsSession(key, startTime, endTime) | ๐ | StoatFlow extension; Long and Instant variants |
Stores factory
StoatFlow's suppliers implement the KS *BytesStoreSupplier interfaces and the *StoreBuilder(...) factories return a KS StoreBuilder<T>. Some signatures take a Serde<T> where KS takes Class<T> (no serde registry); StoreBuilder.withCaching*/withLogging* are honored, and build() throws (StoatFlow builds stores via the registry).
| Method | Status | Notes |
|---|---|---|
| persistentKeyValueStore(name) | โ | RocksDB key-value store |
| inMemoryKeyValueStore(name) | โ | In-memory key-value store |
| persistentWindowStore(name, retention, windowSize, retainDuplicates) | โ | RocksDB window store |
| inMemoryWindowStore(name, retention, windowSize, retainDuplicates) | โ | In-memory window store |
| persistentSessionStore(name, retentionPeriod) | โ | RocksDB session store |
| inMemorySessionStore(name, retentionPeriod) | โ | In-memory session store |
| persistentTimestampedKeyValueStore(name) | โ | Timestamped KV store (KIP-258) |
| persistentTimestampedWindowStore(name, retention, windowSize, retainDuplicates) | โ | Timestamped window store (KIP-258); 4-arg retainDuplicates overload supported |
| inMemoryTimestampedKeyValueStore(name) | โ | In-memory timestamped KV store (KIP-258) |
| inMemoryTimestampedWindowStore(name, ...) | โ | In-memory timestamped window store (KIP-258) |
| persistentVersionedKeyValueStore(name, historyRetention) | โ | Versioned store โ RocksDB (KIP-889) |
| persistentVersionedKeyValueStore(name, historyRetention, segmentInterval) | โ | 3-arg KS overload; segmentInterval accepted but ignored (not segmented) โ logs a warning |
| inMemoryVersionedKeyValueStore(name, historyRetention) | โ | Versioned store โ in-memory (KIP-889) |
| lruMap(name, maxCacheSize) | โ | LRU cache store with bounded entries |
| keyValueStoreBuilder(supplier, keySerde, valueSerde) | โ | Returns a KS StoreBuilder |
| timestampedKeyValueStoreBuilder(supplier, keySerde, valueSerde) | โ | KIP-258 |
| windowStoreBuilder(supplier, keySerde, valueSerde) | โ | Returns a KS StoreBuilder |
| timestampedWindowStoreBuilder(supplier, keySerde, valueSerde) | โ | KIP-258 |
| sessionStoreBuilder(supplier, keySerde, aggSerde) | โ | Returns a KS StoreBuilder |
| versionedKeyValueStoreBuilder(supplier, keySerde, valueSerde) | โ | KIP-889 |
| timestamped{KeyValue,Window} / sessionStoreWithHeadersBuilder(...) | โ | Headers-aware builders (KIP-1271); pass a *WithHeaders supplier |
| KeyValue/Window/Session/VersionedBytesStoreSupplier, StoreBuilder | โ | KS supplier/builder interfaces are present |
KeyLockManager (StoatFlow extension)
Striped lock utility for multi-key atomic sections in user-defined Processors. Not in Kafka Streams.
| Method | Status | Notes |
|---|---|---|
| withLock(key, block) | ๐ | Single-key lock scope |
| withLocks(keys, block) | ๐ | Multi-key lock scope with deadlock-free ordering |
Record headers in state stores (KIP-1271 / KIP-1285)
Opt-in persistence of a record's Headers alongside key/value, across the timestamped KV, versioned, timestamped-window, and session store families.
| Surface | Status | Notes |
|---|---|---|
| Materialized.withRecordHeaders() | ๐ | Per-store opt-in (DSL) |
| dsl.store.format = HEADERS | โ | Global default to headers-aware stores |
| Stores.*WithHeaders(...) suppliers + *WithHeadersBuilder(...) | โ | Processor-API headers stores (all four families) |
| QueryableStoreTypes.*WithHeaders() | โ | Read stored headers back via IQ |
| In-place upgrade (existing store โ headers-aware) | โ | Flip on, restart โ no wipe, no changelog replay |
| Downgrade refusal (headers-aware โ plain) | โ | Like KS, a downgrade is refused with a diagnosed, actionable message rather than a raw RocksDB error โ it is a declared format change, not corruption |
state.format.downgrade = wipe-and-restore | ๐ | StoatFlow-only: acknowledge the downgrade in config and it rebuilds that store from the changelog. KS offers no equivalent โ its only remedy is deleting local state by hand |
| Empty-keyspace downgrade (nothing written in headers mode) | ๐ | Dropped in place, free. KS refuses this case too |
| StreamJoined headers | โ (matches KS) | KS does not persist headers on join buffers |
Interactive Queries
StoatFlow (entry point) methods
| Method | Status | Notes |
|---|---|---|
| store(StoreQueryParameters) | โ | Returns read-only store view |
| storeNames() | โ | List available store names |
| queryMetadataForKey(store, key, serializer) | โ | Returns KeyQueryMetadata โ single-instance: the local host, no standbys, the key's faithful Kafka partition |
| streamsMetadataForStore(store) | โ | The single local host materializing the store |
| metadataForAllStreamsClients() | โ | The one local instance |
| setUncaughtExceptionHandler(handler) | โ | KS StreamsUncaughtExceptionHandler; REPLACE_THREAD triggers an in-place engine restart (fault-storm-guarded โ repeated faults escalate to shutdown), under hot-standby HA as well as without it: the fault is absorbed in place and the pod keeps the active role; SHUTDOWN_CLIENT/SHUTDOWN_APPLICATION shut the single instance down |
| pause() | ๐ | Pause processing with drain semantics |
| unpause() | ๐ | Resume processing after pause |
| awaitState(state, timeout) | ๐ | Wait for target state with timeout |
| stateTransitionHistory() | ๐ | Recent state transitions with timestamps |
StoreQueryParameters
| Method | Status | Notes |
|---|---|---|
| fromNameAndType(name, type) | โ | Factory method |
| enableStaleStores() | โ | Allow queries during RESTORING |
| withPartition(partition) | ๐ | N/A for single-instance |
QueryableStoreTypes
| Method | Status | Notes |
|---|---|---|
| keyValueStore() | โ | Returns ReadOnlyKeyValueStore |
| windowStore() | โ | Returns ReadOnlyWindowStore |
| sessionStore() | โ | Returns ReadOnlySessionStore |
| timestampedKeyValueStore() | โ | Returns ReadOnlyKeyValueStore<K, ValueAndTimestamp<V>> (KIP-258) |
| timestampedWindowStore() | โ | Returns ReadOnlyWindowStore<K, ValueAndTimestamp<V>> (KIP-258) |
| versionedKeyValueStore() | โ | Returns ReadOnlyVersionedKeyValueStore<K, V> (KIP-889) |
withPartition() and inter-instance RPC are unnecessary.IQ metadata (single-instance: always local)
Kafka Streams' host/partition-distribution metadata types exist so cross-instance "find the host, then query" code compiles and runs. Under StoatFlow's single-instance model the answer is always "this host, locally."
| Type | Status | Notes |
|---|---|---|
| HostInfo(host, port) | โ | The local host; sourced from application.server (synthetic localhost:0 if unset) |
| StreamsMetadata | โ | hostInfo(), stateStoreNames(), topicPartitions(); no standbys |
| KeyQueryMetadata | โ | activeHost() = local, no standby hosts, partition() = the key's faithful Kafka partition |
Exception handlers
| Handler | Status | Notes |
|---|---|---|
| DeserializationExceptionHandler | โ | KS-ordered handle(ErrorHandlerContext, ConsumerRecord, Exception); LogAndFail (default), LogAndContinue, DLQ |
| ProcessingExceptionHandler | โ | KS-ordered handle(ErrorHandlerContext, Record, Exception); LogAndFail (default), LogAndContinue, DLQ |
| ProductionExceptionHandler | โ | KS-ordered handle(ErrorHandlerContext, ProducerRecord, Exception); intelligent retry |
| ErrorHandlerContext | โ | KS interface: topic()/partition()/offset()/timestamp()/headers()/processorNodeId()/taskId() |
| StreamsException | โ | Extends KafkaException; StoatFlow's fatal failures extend it, so catch (StreamsException) works |
Lifecycle
| Feature | Status | Notes |
|---|---|---|
| State enum | โ | StoatFlow keeps a richer set than KS's 7 โ CREATED, STARTING, VALIDATING_STATE, RESTORING, RUNNING, DRAINING, PAUSED, STOPPING, STOPPED, ERROR. For a KS-shaped KafkaStreams.State, use the KafkaStreamsState mirror + State.toKafkaStreamsState() / kafkaStreamsState() (KSC-81) |
| StateListener | โ | KS-exact single setStateListener(StateListener) โ a Java method reference / lambda resolves with no cast (KSC-82); an existing BiConsumer<State,State> migrates via StateListener.of(...) |
| StateRestoreListener | โ | Full KS-compatible: onRestoreStart / onBatchRestored / onRestoreEnd |
| StreamsUncaughtExceptionHandler | โ | setUncaughtExceptionHandler(); REPLACE_THREAD = in-place engine restart (fault-storm-guarded; works under hot-standby HA too โ absorbed in place, no failover until the budget is spent), SHUTDOWN_* = single-instance shutdown |
| cleanUp() | โ | Deletes all local state for the application id; valid only in CREATED/STOPPED; rebuilt from changelog on next start |
| close() | โ | Graceful shutdown; blocks until terminal. Every concurrent close caller now blocks until completion (a losing second close() no longer returns early mid-shutdown) โ KSC-83 |
| close(Duration) | โ | The timeout bounds only the caller's wait, never the shutdown work (KS-exact). Duration.ZERO = async signal-and-return; negative โ IllegalArgumentException; shutdown continues after a false return. Returns true on any terminal state incl. ERROR (divergence โ KS only NOT_RUNNING) โ KSC-83 |
| close(CloseOptions) | โ | Mirrors the top-level KIP-1153 io.stoatflow.core.CloseOptions (KS-exact defaults + mutate-and-return-this fluents + @JvmStatic factories). GroupMembershipOperation is a per-call override that always wins over the leaveGroupOnClose config (incl. its implicit REMAIN_IN_GROUP default); no-op under HA assign(). The deprecated nested KIP-812 KafkaStreams.CloseOptions is not mirrored โ a port rewrites one line to the factories โ KSC-83 |
Configuration
| Aspect | Status | Notes |
|---|---|---|
| StreamsConfig builder | โ | Type-safe builder pattern (StreamsConfig.builder(appId, bootstrap).โฆbuild()) โ the primary config model |
Properties / *_CONFIG constants | โ | Canonical KS adapter (ADR-124). StreamsConfig.fromProperties(props) / fromMap(map) adapt a KS-keyed Properties/Map onto the typed builder with complete coverage of the KS-4.3.0 overlap surface โ all 74 keys, listed one by one in KS config-key mapping below: typed mappings, class-name pluggables (serdes, the three exception handlers, default.client.supplier, rocksdb.config.setter), client prefixes (consumer./main.consumer./restore.consumer./producer./admin.), unprefixed client keys routed by per-client validity exactly as Kafka Streams does (security.protocol reaches consumer + producer + admin, acks only the producer; a prefixed spelling wins, and StoatFlow's forced overrides win over both โ main.consumer. is a main-consumer-only scope, as in KS, not an alias for consumer.), and a lenient 3-tier unknown-key policy (mapped / recognised-no-op / unknown) + opt-in stoatflow.config.strict, which throws on truly-unknown keys instead of forwarding them to every client as custom props. The typed builder/YAML stays the recommended model; the adapter is the canonical KS-compat surface, not a stepping stone to a Map-backed swap (ADR-124). The KS *_CONFIG symbol constants and prefix helpers (consumerPrefix(...) โฆ adminClientPrefix(...)) are also shipped for symbol-level source compatibility (KSC-80). |
StreamsConfig(Map) / StreamsConfig(Properties) ctor | โ | KSC-79. The KS-faithful new StreamsConfig(props) / new StreamsConfig(map) bootstrap idiom compiles after the import-swap (Properties binds the single Map<*,*> ctor); it delegates to the same fromMap(...).build() adapter as fromProperties. A missing application.id throws at construction (IllegalArgumentException, where KS throws ConfigException). The typed builder(...) / fromProperties(...) paths stay recommended. |
toProperties() | ๐ | StreamsConfig.toProperties() emits the config back to KS-keyed Properties (resolved-view, masked) โ for /config, diffing, inspect/reconstruct |
default.timestamp.extractor | โ | StreamsConfig.defaultTimestampExtractor โ a global fallback TimestampExtractor for sources with no per-Consumed WatermarkStrategy (event-time basis only; the watermark default is unaffected) |
| Admin client config | โ | StreamsConfig.adminConfig / admin.* prefix โ passthrough merged at all AdminClient sites so a secured cluster authenticates |
| KafkaClientSupplier | โ | Custom client factory for instrumentation/testing |
| DefaultKafkaClientSupplier | โ | Standard KafkaConsumer/KafkaProducer/Admin factory |
| RocksDB config | โ | Bounded memory by default |
| Serdes | โ | Compatible |
| Default key/value Serdes | โ | defaultKeySerde/defaultValueSerde propagate through the DSL |
| Consumer/Producer config | โ | Pass-through maps |
| application.server | โ | host:port for the local host reported by interactive-query metadata; defaults from the runtime HTTP host:port when unset |
| state.cleanup.dir.max.age.ms (KIP-1259) | โ | Age-gated purge of stale local state at startup (rebuilt from changelog); default disabled |
| state.cleanup.delay.ms | โ | Orphaned-store cleanup interval |
KS config-key mapping
Full Kafka Streams 4.3.0 StreamsConfig ConfigDef coverage โ all 74 keys โ as accepted by StreamsConfig.fromProperties / fromMap (ADR-124). A build-time drift test pins this set against the real Kafka Streams ConfigDef, so a key added upstream cannot go unnoticed. Some rows below cover a family of keys.
Status in this table: โ
mapped ยท ๐ added (ADR-124) ยท ๐ก recognised-no-op (warns, never throws โ even under stoatflow.config.strict) ยท ๐งฉ client pass-through (routed to every client whose own configNames() contains it, exactly as Kafka Streams does; a consumer. / producer. / admin. prefix still wins, and StoatFlow's forced overrides win over both).
A port that references the symbol rather than the raw string is covered too (KSC-80): StreamsConfig.TOPOLOGY_OPTIMIZATION_CONFIG, StreamsConfig.METRIC_REPORTER_CLASSES_CONFIG, the six client-prefix constants (CONSUMER_PREFIX / MAIN_CONSUMER_PREFIX / RESTORE_CONSUMER_PREFIX / GLOBAL_CONSUMER_PREFIX / PRODUCER_PREFIX / ADMIN_CLIENT_PREFIX) and the prefix helpers (StreamsConfig.consumerPrefix(prop) โฆ adminClientPrefix(prop)) all ship, and the keys they build route or no-op exactly as the raw strings do. TOPIC_PREFIX and the topology-optimization value constants are not shipped.
| KS key | Status | StoatFlow mapping / reason |
|---|---|---|
application.id | โ | applicationId |
bootstrap.servers | โ | bootstrapServers |
state.dir | โ | stateDir (StoatFlow default /var/stoatflow/state) |
default.key.serde | โ | defaultKeySerde (Class or class-name) |
default.value.serde | โ | defaultValueSerde (Class or class-name) |
num.stream.threads | โ | โ numLanes (closest single-instance analogue) |
processing.guarantee | โ | processingGuarantee (plus the exactly_once_beta alias; StoatFlow defaults to EXACTLY_ONCE where KS defaults to at_least_once โ KSC-84) |
application.server | โ | applicationServer (interactive-query metadata host:port โ KSC-47) |
deserialization.exception.handler (+ deprecated default.deserialization.exception.handler) | ๐ | deserializationExceptionHandler (class-name) |
production.exception.handler (+ deprecated default.production.exception.handler) | ๐ | productionExceptionHandler (class-name) |
processing.exception.handler | ๐ | processingExceptionHandler (class-name) |
default.timestamp.extractor | ๐ | defaultTimestampExtractor (global fallback, event-time basis) |
default.client.supplier | ๐ | kafkaClientSupplier (class-name) |
rocksdb.config.setter | ๐ | rocksDbConfigSetter (class-name) |
dsl.store.format | ๐ | dslStoreFormat (DslStoreFormat.fromConfigString) |
commit.interval.ms | ๐ | โ barrierIntervalMs (a semantic seed; the commit cadence is adaptive) |
buffered.records.per.partition | ๐ | bufferedRecordsPerPartition (keep it >= max.poll.records) |
max.task.idle.ms | ๐ | maxTaskIdleMs (1:1; both allow -1) |
replication.factor | ๐ | changelogReplicationFactor |
state.cleanup.delay.ms / state.cleanup.dir.max.age.ms | ๐ | stateCleanupDelayMs / stateCleanupDirMaxAgeMs (KIP-1259) |
ensure.explicit.internal.resource.naming | ๐ | ensureExplicitNaming (StoatFlow defaults to true, KS to false; the mapping applies only when the key is present) |
processor.wrapper.class | โ ๏ธ | KIP-1112 โ supported. One ProcessorWrapper wraps every node, DSL and Processor API alike, at topology-compile time. Full KS fidelity for Processor API nodes; DSL-internal nodes get the record path only, and replacing an internal node is rejected at build time. A wrapper set on the runtime config alone is honoured, where KS ignores that case as too late |
num.standby.replicas | ๐ก | Single-instance: no standby replicas |
acceptable.recovery.lag / max.warmup.replicas / task.assignor.class / probing.rebalance.interval.ms | ๐ก | Single-instance: no standby, warmup, assignor or probing |
rack.aware.assignment.* / upgrade.from | ๐ก | Single-instance: no rack-aware assignment, no rolling-upgrade protocol |
group.protocol | ๐ก | StoatFlow manages consumer-group membership internally |
statestore.cache.max.bytes / cache.max.bytes.buffering | ๐ก | StoatFlow's cache caps are per-store and commit-triggering (caching.max-entries / caching.max-estimated-bytes), not a global evict budget; global memory is bounded by state.uncommitted-max-bytes |
client.id | ๐ก | Client ids are derived from application.id; override with consumer.client.id / producer.client.id |
task.timeout.ms | ๐ก | Commit-path waits are bounded by barrierTimeoutMs instead |
topology.optimization | ๐ก | Re-keying is in-memory; KTable source reuse is autoReuseKTableSourceTopics |
repartition.purge.interval.ms | ๐ก | In-memory re-keying: there are no repartition topics to purge |
processing.exception.handler.global.enabled | ๐ก | The processing handler is already applied globally |
errors.dead.letter.queue.topic.name | ๐ก | The DLQ is configured per handler (DeadLetterQueue*ExceptionHandler) |
default.dsl.store / dsl.store.suppliers.class | ๐ก | Choose the store with Materialized.withStoreType |
default.list.{key,value}.serde.{inner,type} | ๐ก | No ListSerde default-type config; use explicit serdes |
windowstore.changelog.additional.retention.ms | ๐ก | Window changelogs use compaction; no extra retention |
window.size.ms / windowed.inner.class.serde | ๐ก | Deprecated in KS; use the typed WindowedSerdes |
built.in.metrics.version / metric.reporters / metrics.* / enable.metrics.push | ๐ก | Metrics go through Micrometer, not the Kafka metrics subsystem |
config.providers | ๐ก | Resolve externalised config before constructing StreamsConfig |
allow.os.group.write.access | ๐ก | StoatFlow does not relax state-dir permissions via config |
log.summary.interval.ms | ๐ก | No periodic summary-log toggle |
poll.ms | ๐ก | Use consumerPollTimeoutMs |
security.protocol / connections.max.idle.ms / metadata.max.age.ms / {receive,send}.buffer.bytes / request.timeout.ms / reconnect.backoff.* / retry.backoff.ms / metadata.recovery.* | ๐งฉ | Client properties, routed unprefixed to every client they are valid for (KSC-92, KS parity): security.protocol reaches consumer, producer and admin; the rest go by their own ConfigDef. A consumer. / main.consumer. / producer. / admin. prefix overrides the bare form. The same routing applies to bare ssl.* and sasl.*, and to producer-only keys such as acks |
Key architectural differences
The runtime-model deltas โ single instance vs. rebalancing cluster, lanes vs. partition-bound tasks, in-memory re-keying vs. repartition topics, barrier-based exactly-once, global vs. partition-scoped state โ are summarized in the canonical deltas-at-a-glance table, with the conceptual treatment on the same page. The rows below are the operational and time-semantics differences that surface while reading this matrix:
| Aspect | Kafka Streams | StoatFlow |
|---|---|---|
| RocksDB memory | Unbounded default | Bounded default (256 MiB) |
| Window closure | Stream-time based | Watermark-based |
| Grace period | Retention after close | Extends acceptance window |
| Watermark strategy | N/A | Flink-style WatermarkStrategy |
| Punctuators | Task-scoped, on the stream thread | Dedicated punctuation lane per sub-topology, full state access |
| Key-based timers | N/A | Flink-inspired TimerService |
| Scheduled sources | N/A | Interval & cron-based emission |
| Runtime control | N/A | pause()/unpause() |
Test utilities
TopologyTestDriver drives a topology in-memory (no broker) for unit tests. All state stores โ including RocksDB-Materialized ones โ run in-memory in the test driver; real-engine + RocksDB end-to-end coverage uses integration tests with an embedded broker.
TopologyTestDriver
| Member | Status | Notes |
|---|---|---|
new TopologyTestDriver(topology) / (topology, Properties) / (topology, Instant) / (topology, Properties, Instant) | โ | KS-shaped constructors; Properties via StreamsConfig.fromProperties |
createInputTopic(name, Serde/Serializer, โฆ) | โ | Serde and Serializer overloads + 5-arg (โฆ, Instant, Duration) start-time/auto-advance form (serdes accepted, ignored) |
createOutputTopic(name, Serde/Deserializer, โฆ) | โ | Serde and Deserializer overloads |
getAllStateStores() / producedTopicNames() | โ | All stores by name; the set of produced topics |
advanceWallClockTime(Duration) / get*Store(name) / close() | โ |
TestRecord
KS positional order (key, value, headers, timestamp); ctors (K,V), (K,V,Instant), (K,V,Headers[,Instant]), value-only (V), (ConsumerRecord), (ProducerRecord); bean getters getKey()/getValue()/getHeaders()/getRecordTime() + KS accessors key()/value()/headers()/timestamp().
TestInputTopic
| Method | Status | Notes |
|---|---|---|
| pipeInput(key, value , timestamp) | โ | timestamp as long or Instant |
| pipeInput(value) / pipeInput(value, Instant) | โ | Value-only (null key) โ for value-centric / Avro tests |
| pipeKeyValueList / pipeValueList / pipeRecordList | โ | Bulk piping; key-value lists use KeyValue; timed (โฆ, Instant, Duration) overloads; pipeRecordList is out-variant |
| advanceTime(Duration) | โ | Advance the input topic's record clock |
TestOutputTopic
| Method | Status | Notes |
|---|---|---|
| readKeyValue() / readKeyValuesToList() | โ | Return KeyValue<K, V> (.key / .value), not Pair |
| readKeyValuesToMap() | โ | |
| readValue() / readRecord() | โ |
KeyValue is StoatFlow's KS-shaped key-value type (the same one the DSL uses); a KS port rewrites the KeyValue import to match.
High availability (hot-standby)
Not a KS-parity surface โ Kafka Streams scales out with standby tasks; StoatFlow's opt-in hot-standby pairs one active with one or more warm standbys. The surface, and the two deliberate gaps:
| Surface | Status | Notes |
|---|---|---|
stoatflow.ha.mode = off / active-standby | ๐ | Deploy-time opt-in; off (default) is byte-identical to single-instance |
| Active/passive failover | ๐ | Crash failover in seconds; graceful deploys / /ha/switch hand off cleanly |
| Split-brain fencing | ๐ | EOS: broker-enforced producer-epoch fence; ALO: metadata-topic lease |
/ha/{status,switch,promote,demote} endpoints | ๐ | Operator shims; SWITCH/PROMOTE/DEMOTE return 409 unless a caught-up target exists (?force=true override) |
ha.acceptable-recovery-lag | ๐ | A single total lag sum (default 50000) โ not per-task like KS acceptable.recovery.lag (10000) |
| Continuous changelog replication | ๐ | The standby streams committed changelog deltas into local state in real time โ like KS standby tasks' continuous restore, for the single-active pair |
| Restore-before-process on promotion | ๐ | A promoting pod restores local state to the committed changelog end before processing |
| Queryable standby (KIP-535 IQ from standby) | โ | Standbys are non-queryable; store queries on a standby are rejected |
KS standby tasks / num.standby.replicas | โ (different model) | Single-active with warm standbys โ not N-instance standby tasks |
See High availability for the operational guide.
Related
- Comparison matrix โ feature-by-feature against Kafka Streams and self-hosted / managed Flink
- Building โ how-to guidance for the operators above
- KStream and KTable ยท Aggregations ยท Windowing ยท Joins ยท Processor API
- State stores ยท Scheduled sources
Maven reference
StoatFlow's Maven support โ the stoatflow-bom, the stoatflow-parent convention POM, and the stoatflow-maven-plugin. The three artifacts, every <properties> key, the four goals, and the pinned plugin versions.
Metrics reference
The full catalogue of Prometheus meters StoatFlow exposes on /metrics โ name, type, tags, and what each one measures, grouped by area.