Kafka Streams compatibility matrix

Operator-by-operator parity between the StoatFlow DSL and the Kafka Streams DSL โ€” implemented, partial, not yet implemented, deprecated, incompatible, and StoatFlow extensions.

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.

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.

This matrix is transcribed from the in-repository compatibility reference and curated for the public docs. Status reflects the current alpha (1.0.0-alpha.16); the surface is still moving. A small number of rows differ in signature from Kafka Streams (marked โš ๏ธ) or are intentionally absent (marked ๐Ÿ›‘) โ€” read the notes.

Legend

SymbolMeaning
โœ…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

StreamsBuilder

MethodStatusNotes
StreamsBuilder()โœ…No-arg
StreamsBuilder(TopologyConfig)โœ…KSC-77 โ€” seeds build-time defaults (serdes, DSL store format) from a TopologyConfig projection of StreamsConfig. Named-topology / per-topology overrides N/A (single-instance); a non-default processor.wrapper.class throws
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.

MethodStatusNotes
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.

MemberStatusNotes
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

MethodStatusNotes
filter(predicate)โœ…
filterNot(predicate)โœ…
map(mapper)โœ…Key-changing โ†’ sub-topology boundary
mapValues(ValueMapper)โœ…
mapValues(ValueMapperWithKey)โœ…Key-aware value mapper variant
flatMap(mapper)โœ…Key-changing โ†’ sub-topology boundary
flatMapValues(ValueMapper)โœ…
flatMapValues(ValueMapperWithKey)โœ…Key-aware value mapper variant
selectKey(keySelector)โœ…Key-changing โ†’ sub-topology boundary
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

MethodStatusNotes
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

MethodStatusNotes
process(processorSupplier, named?, storeNames...)โœ…Key-changing; ProcessorSupplier. Connects the named stores (and any declared via supplier.stores())
processValues(processorSupplier, named?, storeNames...)โœ…Non-key-changing; FixedKeyProcessorSupplier + named/declared stores
transform()๐Ÿ›‘Removed from KStream in KS 4.x; use process()
transformValues()๐Ÿ›‘Removed from KStream in KS 4.x; use processValues()

KStream โ€” grouping

MethodStatusNotes
groupByKey()โœ…Returns KGroupedStream
groupByKey(Grouped)โœ…With Serdes configuration
groupBy(keySelector)โœ…Key-changing โ†’ sub-topology boundary
groupBy(keySelector, Grouped)โœ…With Serdes configuration

KStream โ€” joins

MethodStatusNotes
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
Temporal join semantics (KIP-914). When the backing table uses a versioned store (KIP-889), stream-table joins perform a temporal lookup against the stream record's timestamp โ€” returning the table value that was valid at the stream event time, so results stay correct when records arrive out of order. Stream-table joins are otherwise point-in-time lookups; a table update does not re-trigger the join.

KStream โ€” branching (BranchedKStream)

MethodStatusNotes
branch(predicate)โœ…
branch(predicate, branched)โœ…With Branched config
defaultBranch()โœ…
defaultBranch(branched)โœ…With Branched config
noDefaultBranch()โœ…

KGroupedStream

MethodStatusNotes
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, โ€ฆ>.

MethodStatusNotes
count()โœ…With Materialized overloads (plain key)
reduce(reducer)โœ…With Materialized overloads
aggregate(initializer, aggregator)โœ…With Materialized overloads
emitStrategy()โœ…OnWindowUpdate (default), OnWindowClose

SessionWindowedKStream

MethodStatusNotes
count()โœ…With session merger
reduce(reducer)โœ…Reducer doubles as merger
aggregate(initializer, aggregator, sessionMerger)โœ…With session merger
emitStrategy()โœ…OnWindowUpdate (default), OnWindowClose

Cogrouped streams

CogroupedKStream

MethodStatusNotes
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

MethodStatusNotes
aggregate(initializer)โœ…With Materialized overloads
emitStrategy()โœ…OnWindowUpdate (default), OnWindowClose

SessionWindowedCogroupedKStream

MethodStatusNotes
aggregate(initializer, sessionMerger)โœ…With Materialized overloads; (initializer, sessionMerger) argument order matches Kafka Streams
emitStrategy()โœ…OnWindowUpdate (default), OnWindowClose

KTable โ€” transformations

MethodStatusNotes
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; sub-topology boundary
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)

KTable โ€” primary-key joins

Both tables share key type K. No co-partitioning required (global state model).

MethodStatusNotes
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
KIP-914 semantics. When both tables are backed by versioned stores, out-of-order records do not trigger join evaluation, and temporal lookups return the correct value from the other table โ€” preventing inconsistent results under out-of-order arrival.

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.

MethodStatusNotes
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.

MethodStatusNotes
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

MethodStatusNotes
queryableStoreName()โœ…Returns underlying table's store name
asKTable()๐Ÿš€StoatFlow extension to unwrap
GlobalKTable wraps KTable internally. Because all state is global, its behaviour is identical to KTable.

Processor API

ProcessorContext

MethodStatusNotes
forward(record)โœ…Forward to downstream processors
forward(record, childName)โœ…Forward to specific child
getStateStore(name)โœ…Access connected state stores
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

TypeStatusNotes
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()
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 / MethodStatusNotes
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
Kafka Streams punctuators are partition-scoped with writable state and always non-blocking. StoatFlow punctuators run globally with read-only state on dedicated virtual threads; PunctuatorMode and the key-based TimerService (which fires in the key's affinity lane 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

MethodStatusNotes
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 read-only state store
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

MethodStatusNotes
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
Cron-based scheduling always uses wall-clock time.

Functional interfaces

KS-compatible functional interfaces backing the Java-friendly DSL overloads.

InterfaceStatusUsed 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

MethodStatusNotes
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

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).

MethodStatusNotes
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

MethodStatusNotes
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

MethodStatusNotes
with(keySerde, valueSerde)โœ…
keySerde(serde)โœ…
valueSerde(serde)โœ…
as(name)โœ…
streamPartitioner(partitioner)โœ…

Materialized

MethodStatusNotes
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

MethodStatusNotes
as(name)โœ…
withName(name)โœ…
withFunction(chain)โœ…
withConsumer(consumer)โœ…Java Consumer overload

Named

Named is open/subclassable, so KSML's name-validation subclasses work.

MethodStatusNotes
as(name)โœ…
withName(name)โœ…Fluent builder form
name()โœ…Read the configured name

Printed

MethodStatusNotes
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.

MethodStatusNotes
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

MethodStatusNotes
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

MethodStatusNotes
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

MethodStatusNotes
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

MethodStatusNotes
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

MethodStatusNotes
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

MethodStatusNotes
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
Suppression buffers are flushed on watermark advancement โ€” 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.
KIP-914:suppress() throws a validation exception for tables backed by versioned stores โ€” their temporal semantics conflict with suppression.

BufferConfig

MethodStatusNotes
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

MethodStatusNotes
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)
StoatFlow's factory methods take 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

TypeStatusNotes
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

MetricStatusNotes
num-keys (KIP-1250)โœ…Entry-count gauge for in-memory KV/window/session stores

KeyValueStore

MethodStatusNotes
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
reverseAll()โœ…Read-your-writes semantics
reverseRange(from, to)โœ…Read-your-writes semantics
approximateNumEntries()โœ…
prefixScan()โœ…KIP-614; read-your-writes semantics

ReadOnlyKeyValueStore

MethodStatusNotes
get(key)โœ…
containsKey(key)๐Ÿš€StoatFlow extension; bloom filter optimization for RocksDB
all()โœ…
range(from, to)โœ…
reverseAll()โœ…
reverseRange(from, to)โœ…
approximateNumEntries()โœ…
prefixScan(prefix, serializer)โœ…KIP-614

WindowStore

Key range queries require K : Comparable<K>. All Instant variants delegate to the Long variants.

MethodStatusNotes
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
backwardFetch(keyFrom, keyTo, timeFrom, timeTo)โœ…Reverse key range; Long and Instant variants
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

MethodStatusNotes
All query methods from WindowStoreโœ…16 query methods total
containsKey(key, windowStartTime)๐Ÿš€StoatFlow extension; Long and Instant variants

SessionStore

MethodStatusNotes
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
backwardFindSessions(keyFrom, keyTo, earliestEndTime, latestStartTime)โœ…Key range; Long and Instant variants
fetch(key)โœ…All sessions for single key
backwardFetch(key)โœ…Reverse order
fetch(keyFrom, keyTo)โœ…Key range queries
backwardFetch(keyFrom, keyTo)โœ…Reverse order
findAll()๐Ÿš€StoatFlow extension
expireSessions(watermark)๐Ÿš€StoatFlow extension
approximateNumEntries()๐Ÿš€StoatFlow extension

ReadOnlySessionStore

MethodStatusNotes
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).

MethodStatusNotes
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.

MethodStatusNotes
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.

SurfaceStatusNotes
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
StreamJoined headersโŒ (matches KS)KS does not persist headers on join buffers

Interactive Queries

StoatFlow (entry point) methods

MethodStatusNotes
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; every response shuts the instance down (REPLACE_THREAD โ‰ก SHUTDOWN_APPLICATION)
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

MethodStatusNotes
fromNameAndType(name, type)โœ…Factory method
enableStaleStores()โœ…Allow queries during RESTORING
withPartition(partition)๐Ÿ›‘N/A for single-instance

QueryableStoreTypes

MethodStatusNotes
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)
The global state model eliminates partition routing โ€” all state is locally accessible from a single process, so 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."

TypeStatusNotes
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

HandlerStatusNotes
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

FeatureStatusNotes
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(); single-instance: every response shuts the instance down
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

AspectStatusNotes
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 (74 keys): 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.), and a lenient 3-tier unknown-key policy (mapped / recognised-no-op / unknown) + opt-in stoatflow.config.strict that throws only on truly-unknown keys. 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

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:

AspectKafka StreamsStoatFlow
RocksDB memoryUnbounded defaultBounded default (256 MB)
Window closureStream-time basedWatermark-based
Grace periodRetention after closeExtends acceptance window
Watermark strategyN/AFlink-style WatermarkStrategy
PunctuatorsTask-scoped, on the stream threadDedicated punctuation lane per sub-topology, full state access
Key-based timersN/AFlink-inspired TimerService
Scheduled sourcesN/AInterval & cron-based emission
Runtime controlN/Apause()/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

MemberStatusNotes
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

MethodStatusNotes
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

MethodStatusNotes
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:

SurfaceStatusNotes
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.