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.
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-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
| 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 |
StreamsBuilder
| Method | Status | Notes |
|---|---|---|
| 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.
| 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 โ 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
| 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()) |
| 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
| Method | Status | Notes |
|---|---|---|
| groupByKey() | โ | Returns KGroupedStream |
| groupByKey(Grouped) | โ | With Serdes configuration |
| groupBy(keySelector) | โ | Key-changing โ sub-topology boundary |
| 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; 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).
| 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 |
| 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() |
| 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 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 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
| 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() 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 |
| reverseAll() | โ | Read-your-writes semantics |
| reverseRange(from, to) | โ | Read-your-writes semantics |
| 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) | โ | |
| 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.
| 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 |
| 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
| 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 |
| 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
| 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 |
| 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; 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
| 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(); 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
| 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 (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:
| Aspect | Kafka Streams | StoatFlow |
|---|---|---|
| RocksDB memory | Unbounded default | Bounded default (256 MB) |
| 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.