[{"data":1,"prerenderedAt":1899},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fconcepts\u002Fhow-stoatflow-differs-from-ks":314,"\u002Fdocs\u002Fconcepts\u002Fhow-stoatflow-differs-from-ks-surround":1894},[4,38,86,149,176,219,257,273],{"title":5,"path":6,"stem":7,"children":8,"icon":37},"Getting Started","\u002Fdocs\u002Fgetting-started","1.docs\u002F1.getting-started\u002F1.index",[9,12,17,22,27,32],{"title":10,"path":6,"stem":7,"icon":11},"Getting started","i-lucide-flag",{"title":13,"path":14,"stem":15,"icon":16},"Modules overview","\u002Fdocs\u002Fgetting-started\u002Fmodules-overview","1.docs\u002F1.getting-started\u002F2.modules-overview","i-lucide-boxes",{"title":18,"path":19,"stem":20,"icon":21},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","1.docs\u002F1.getting-started\u002F3.installation","i-lucide-download",{"title":23,"path":24,"stem":25,"icon":26},"License configuration","\u002Fdocs\u002Fgetting-started\u002Flicense-configuration","1.docs\u002F1.getting-started\u002F4.license-configuration","i-lucide-key-round",{"title":28,"path":29,"stem":30,"icon":31},"Your first app","\u002Fdocs\u002Fgetting-started\u002Ffirst-app","1.docs\u002F1.getting-started\u002F5.first-app","i-lucide-square-play",{"title":33,"path":34,"stem":35,"icon":36},"Project setup","\u002Fdocs\u002Fgetting-started\u002Fproject-setup","1.docs\u002F1.getting-started\u002F6.project-setup","i-lucide-package",false,{"title":39,"path":40,"stem":41,"children":42,"icon":45},"Core Concepts","\u002Fdocs\u002Fconcepts","1.docs\u002F2.concepts\u002F1.index",[43,46,51,56,61,66,71,76,81],{"title":44,"path":40,"stem":41,"icon":45},"Core concepts","i-lucide-book-open",{"title":47,"path":48,"stem":49,"icon":50},"Architecture","\u002Fdocs\u002Fconcepts\u002Farchitecture","1.docs\u002F2.concepts\u002F2.architecture","i-lucide-layers",{"title":52,"path":53,"stem":54,"icon":55},"Exactly-once semantics","\u002Fdocs\u002Fconcepts\u002Fexactly-once","1.docs\u002F2.concepts\u002F3.exactly-once","i-lucide-shield-check",{"title":57,"path":58,"stem":59,"icon":60},"Lanes and parallelism","\u002Fdocs\u002Fconcepts\u002Flanes-and-parallelism","1.docs\u002F2.concepts\u002F4.lanes-and-parallelism","i-lucide-split",{"title":62,"path":63,"stem":64,"icon":65},"State and thread-safety","\u002Fdocs\u002Fconcepts\u002Fstate-and-thread-safety","1.docs\u002F2.concepts\u002F5.state-and-thread-safety","i-lucide-lock",{"title":67,"path":68,"stem":69,"icon":70},"Event time and watermarks","\u002Fdocs\u002Fconcepts\u002Fevent-time-and-watermarks","1.docs\u002F2.concepts\u002F6.event-time-and-watermarks","i-lucide-clock",{"title":72,"path":73,"stem":74,"icon":75},"The configuration model","\u002Fdocs\u002Fconcepts\u002Fconfiguration-model","1.docs\u002F2.concepts\u002F7.configuration-model","i-lucide-sliders-horizontal",{"title":77,"path":78,"stem":79,"icon":80},"The error-handling model","\u002Fdocs\u002Fconcepts\u002Ferror-handling-model","1.docs\u002F2.concepts\u002F8.error-handling-model","i-lucide-triangle-alert",{"title":82,"path":83,"stem":84,"icon":85},"How StoatFlow differs from Kafka Streams","\u002Fdocs\u002Fconcepts\u002Fhow-stoatflow-differs-from-ks","1.docs\u002F2.concepts\u002F9.how-stoatflow-differs-from-ks","i-lucide-arrow-left-right",{"title":87,"path":88,"stem":89,"children":90,"icon":93},"Building Topologies","\u002Fdocs\u002Fbuilding","1.docs\u002F3.building\u002F1.index",[91,94,99,104,109,114,119,124,129,134,139,144],{"title":92,"path":88,"stem":89,"icon":93},"Building topologies","i-lucide-blocks",{"title":95,"path":96,"stem":97,"icon":98},"Error handling and DLQ","\u002Fdocs\u002Fbuilding\u002Ferror-handling-dlq","1.docs\u002F3.building\u002F10.error-handling-dlq","i-lucide-circle-x",{"title":100,"path":101,"stem":102,"icon":103},"State stores","\u002Fdocs\u002Fbuilding\u002Fstate-stores","1.docs\u002F3.building\u002F11.state-stores","i-lucide-database",{"title":105,"path":106,"stem":107,"icon":108},"Testing topologies","\u002Fdocs\u002Fbuilding\u002Ftesting","1.docs\u002F3.building\u002F12.testing","i-lucide-flask-conical",{"title":110,"path":111,"stem":112,"icon":113},"Sources and sinks","\u002Fdocs\u002Fbuilding\u002Fstreams-builder","1.docs\u002F3.building\u002F2.streams-builder","i-lucide-import",{"title":115,"path":116,"stem":117,"icon":118},"KStream and KTable operations","\u002Fdocs\u002Fbuilding\u002Fkstream-ktable","1.docs\u002F3.building\u002F3.kstream-ktable","i-lucide-waypoints",{"title":120,"path":121,"stem":122,"icon":123},"Aggregations","\u002Fdocs\u002Fbuilding\u002Faggregations","1.docs\u002F3.building\u002F4.aggregations","i-lucide-sigma",{"title":125,"path":126,"stem":127,"icon":128},"Windowing","\u002Fdocs\u002Fbuilding\u002Fwindowing","1.docs\u002F3.building\u002F5.windowing","i-lucide-calendar-clock",{"title":130,"path":131,"stem":132,"icon":133},"Joins","\u002Fdocs\u002Fbuilding\u002Fjoins","1.docs\u002F3.building\u002F6.joins","i-lucide-git-merge",{"title":135,"path":136,"stem":137,"icon":138},"The Processor API","\u002Fdocs\u002Fbuilding\u002Fprocessor-api","1.docs\u002F3.building\u002F7.processor-api","i-lucide-cpu",{"title":140,"path":141,"stem":142,"icon":143},"Scheduled sources","\u002Fdocs\u002Fbuilding\u002Fscheduled-sources","1.docs\u002F3.building\u002F8.scheduled-sources","i-lucide-timer",{"title":145,"path":146,"stem":147,"icon":148},"Serdes and Avro","\u002Fdocs\u002Fbuilding\u002Fserdes","1.docs\u002F3.building\u002F9.serdes","i-lucide-binary",{"title":150,"path":151,"stem":152,"children":153,"icon":75},"Configuration","\u002Fdocs\u002Fconfiguration","1.docs\u002F4.configuration\u002F1.index",[154,156,161,166,171],{"title":155,"path":151,"stem":152,"icon":75},"How configuration works",{"title":157,"path":158,"stem":159,"icon":160},"Engine configuration (:core)","\u002Fdocs\u002Fconfiguration\u002Fcore-config","1.docs\u002F4.configuration\u002F2.core-config","i-lucide-settings-2",{"title":162,"path":163,"stem":164,"icon":165},"Runtime configuration (:runtime)","\u002Fdocs\u002Fconfiguration\u002Fruntime-config","1.docs\u002F4.configuration\u002F3.runtime-config","i-lucide-server-cog",{"title":167,"path":168,"stem":169,"icon":170},"Defaults, adaptivity, and presets","\u002Fdocs\u002Fconfiguration\u002Fdefaults-and-presets","1.docs\u002F4.configuration\u002F4.defaults-and-presets","i-lucide-gauge",{"title":172,"path":173,"stem":174,"icon":175},"Kafka client configuration","\u002Fdocs\u002Fconfiguration\u002Fkafka-client-config","1.docs\u002F4.configuration\u002F5.kafka-client-config","i-lucide-plug",{"title":177,"path":178,"stem":179,"children":180,"icon":183},"Running in Production","\u002Fdocs\u002Fruntime","1.docs\u002F5.runtime\u002F1.index",[181,184,189,194,199,204,209,214],{"title":182,"path":178,"stem":179,"icon":183},"The runtime","i-lucide-server",{"title":185,"path":186,"stem":187,"icon":188},"The REST API","\u002Fdocs\u002Fruntime\u002Frest-api","1.docs\u002F5.runtime\u002F2.rest-api","i-lucide-globe",{"title":190,"path":191,"stem":192,"icon":193},"Health checks","\u002Fdocs\u002Fruntime\u002Fhealth-checks","1.docs\u002F5.runtime\u002F3.health-checks","i-lucide-heart-pulse",{"title":195,"path":196,"stem":197,"icon":198},"Metrics","\u002Fdocs\u002Fruntime\u002Fmetrics","1.docs\u002F5.runtime\u002F4.metrics","i-lucide-activity",{"title":200,"path":201,"stem":202,"icon":203},"Pause and resume","\u002Fdocs\u002Fruntime\u002Fpause-unpause","1.docs\u002F5.runtime\u002F5.pause-unpause","i-lucide-pause",{"title":205,"path":206,"stem":207,"icon":208},"Plugins and lifecycle hooks","\u002Fdocs\u002Fruntime\u002Fplugins","1.docs\u002F5.runtime\u002F6.plugins","i-lucide-puzzle",{"title":210,"path":211,"stem":212,"icon":213},"Docker images","\u002Fdocs\u002Fruntime\u002Fdocker","1.docs\u002F5.runtime\u002F7.docker","i-lucide-container",{"title":215,"path":216,"stem":217,"icon":218},"GraalVM native image","\u002Fdocs\u002Fruntime\u002Fnative-image","1.docs\u002F5.runtime\u002F8.native-image","i-lucide-zap",{"title":220,"path":221,"stem":222,"children":223,"icon":226},"Deploying & Operating","\u002Fdocs\u002Foperating","1.docs\u002F6.operating\u002F1.index",[224,227,232,237,242,247,252],{"title":225,"path":221,"stem":222,"icon":226},"Deploying and operating","i-lucide-life-buoy",{"title":228,"path":229,"stem":230,"icon":231},"Running on Kubernetes","\u002Fdocs\u002Foperating\u002Fkubernetes","1.docs\u002F6.operating\u002F2.kubernetes","i-lucide-ship",{"title":233,"path":234,"stem":235,"icon":236},"High availability","\u002Fdocs\u002Foperating\u002Fhigh-availability","1.docs\u002F6.operating\u002F3.high-availability","i-lucide-copy",{"title":238,"path":239,"stem":240,"icon":241},"Liveness and readiness probes","\u002Fdocs\u002Foperating\u002Fprobes","1.docs\u002F6.operating\u002F4.probes","i-lucide-stethoscope",{"title":243,"path":244,"stem":245,"icon":246},"Observability","\u002Fdocs\u002Foperating\u002Fobservability","1.docs\u002F6.operating\u002F5.observability","i-lucide-telescope",{"title":248,"path":249,"stem":250,"icon":251},"Tuning under load","\u002Fdocs\u002Foperating\u002Ftuning","1.docs\u002F6.operating\u002F6.tuning","i-lucide-sliders",{"title":253,"path":254,"stem":255,"icon":256},"Production checklist","\u002Fdocs\u002Foperating\u002Fproduction-checklist","1.docs\u002F6.operating\u002F7.production-checklist","i-lucide-clipboard-check",{"title":258,"path":259,"stem":260,"children":261,"icon":85},"Migrating from Kafka Streams","\u002Fdocs\u002Fmigration","1.docs\u002F7.migration\u002F1.index",[262,263,268],{"title":258,"path":259,"stem":260,"icon":85},{"title":264,"path":265,"stem":266,"icon":267},"Migration without carrying state","\u002Fdocs\u002Fmigration\u002Fwithout-data-migration","1.docs\u002F7.migration\u002F2.without-data-migration","i-lucide-sparkles",{"title":269,"path":270,"stem":271,"icon":272},"Migration carrying state","\u002Fdocs\u002Fmigration\u002Fwith-data-migration","1.docs\u002F7.migration\u002F3.with-data-migration","i-lucide-database-backup",{"title":274,"path":275,"stem":276,"children":277,"icon":279},"Reference","\u002Fdocs\u002Freference","1.docs\u002F8.reference\u002F1.index",[278,280,285,290,295,300,305,309],{"title":274,"path":275,"stem":276,"icon":279},"i-lucide-list",{"title":281,"path":282,"stem":283,"icon":284},"Configuration reference","\u002Fdocs\u002Freference\u002Fconfiguration-reference","1.docs\u002F8.reference\u002F2.configuration-reference","i-lucide-table",{"title":286,"path":287,"stem":288,"icon":289},"REST API reference","\u002Fdocs\u002Freference\u002Frest-api-reference","1.docs\u002F8.reference\u002F3.rest-api-reference","i-lucide-network",{"title":291,"path":292,"stem":293,"icon":294},"Gradle plugin reference","\u002Fdocs\u002Freference\u002Fgradle-plugin-reference","1.docs\u002F8.reference\u002F4.gradle-plugin-reference","i-lucide-box",{"title":296,"path":297,"stem":298,"icon":299},"Maven reference","\u002Fdocs\u002Freference\u002Fmaven-reference","1.docs\u002F8.reference\u002F5.maven-reference","i-simple-icons-apachemaven",{"title":301,"path":302,"stem":303,"icon":304},"Kafka Streams compatibility matrix","\u002Fdocs\u002Freference\u002Fks-compatibility-matrix","1.docs\u002F8.reference\u002F6.ks-compatibility-matrix","i-lucide-table-2",{"title":306,"path":307,"stem":308,"icon":170},"Metrics reference","\u002Fdocs\u002Freference\u002Fmetrics-reference","1.docs\u002F8.reference\u002F7.metrics-reference",{"title":310,"path":311,"stem":312,"icon":313},"Glossary","\u002Fdocs\u002Freference\u002Fglossary","1.docs\u002F8.reference\u002F8.glossary","i-lucide-book-a",{"id":315,"title":82,"body":316,"description":1887,"extension":1888,"meta":1889,"navigation":1890,"pageType":1891,"path":83,"seo":1892,"stem":84,"__hash__":1893},"docs\u002F1.docs\u002F2.concepts\u002F9.how-stoatflow-differs-from-ks.md",{"type":317,"value":318,"toc":1876},"minimark",[319,344,395,400,449,452,995,1010,1035,1039,1042,1052,1066,1087,1091,1094,1100,1113,1118,1122,1130,1143,1245,1262,1480,1489,1493,1500,1505,1518,1524,1528,1531,1550,1558,1580,1586,1590,1593,1615,1655,1667,1671,1829,1834,1838,1872],[320,321,322,323,327,328,331,332,335,336,339,340,343],"p",{},"If you know Kafka Streams, the fastest way to understand StoatFlow is by the differences. The DSL you write is the same — the same ",[324,325,326],"code",{},"StreamsBuilder",", the same ",[324,329,330],{},"KStream"," \u002F ",[324,333,334],{},"KTable",", the same operators. What changes is the runtime model underneath: how many processes run, where state lives, how parallelism is expressed, how re-keying happens, and how exactly-once is committed. This page lays out those deltas. For the model in its own right, start with ",[337,338,47],"a",{"href":48},"; for porting an existing topology, see ",[337,341,342],{"href":259},"Migration",".",[345,346,347],"tldr-panel",{},[348,349,350,358,371,377,383,389],"ul",{},[351,352,353,357],"li",{},[354,355,356],"strong",{},"One instance, no rebalancing"," — a StoatFlow app runs as exactly one JVM, not a cluster of stream threads that join and leave a group.",[351,359,360,363,364,331,367,370],{},[354,361,362],{},"In-memory re-keying"," — ",[324,365,366],{},"selectKey",[324,368,369],{},"groupBy"," \u002F key-changing joins hand records between lanes in-process; there is no internal repartition topic.",[351,372,373,376],{},[354,374,375],{},"Barrier-based exactly-once"," — one Kafka transaction per commit barrier covers the whole topology, rather than per-task transactional writes.",[351,378,379,382],{},[354,380,381],{},"Global state"," — every store lives in the one process and any key is reachable from anywhere; no partition-scoped isolation, no co-partitioning requirement.",[351,384,385,388],{},[354,386,387],{},"Lanes, not tasks"," — parallelism comes from key-affinity lanes that scale with cores, not from one task per input partition.",[351,390,391,394],{},[354,392,393],{},"The DSL is the same."," Your topology code reads the same; the engine that runs it is different.",[396,397,399],"h2",{"id":398},"what-carries-over-unchanged-the-dsl","What carries over unchanged: the DSL",[320,401,402,403,406,407,409,410,409,412,409,414,417,418,331,421,331,424,331,427,430,431,409,434,409,437,409,440,409,443,446,447,343],{},"The thing you spend most of your time on — the topology — is the part that does ",[354,404,405],{},"not"," change. StoatFlow implements the Kafka Streams DSL: ",[324,408,326],{},", ",[324,411,330],{},[324,413,334],{},[324,415,416],{},"KGroupedStream",", the windowed and session variants, the joins, the Processor API, the ",[324,419,420],{},"Consumed",[324,422,423],{},"Produced",[324,425,426],{},"Materialized",[324,428,429],{},"Grouped"," config objects, and the KS-compatible functional interfaces (",[324,432,433],{},"ValueMapper",[324,435,436],{},"KeyValueMapper",[324,438,439],{},"ValueJoiner",[324,441,442],{},"Reducer",[324,444,445],{},"Aggregator",", …). The full method-by-method status is in the ",[337,448,301],{"href":302},[320,450,451],{},"Concretely, this map-filter topology is written the same way against StoatFlow as against Kafka Streams:",[453,454,456,709],"code-tabs",{"group":455},"lang",[457,458,464],"pre",{"className":459,"code":460,"filename":461,"language":462,"meta":463,"style":463},"language-kotlin shiki shiki-themes vitesse-light","val intermediate =\n    stream1\n        .selectKey { _, _ -> \"lala\" }\n        .map { key, value -> KeyValue(value.substring(0, 3), \"$key:$value\") }\n        .filter { _, value -> value.length > 5 }\n        .mapValues { value -> value.uppercase() }\n\nintermediate.to(\n    \"output-topic\",\n    Produced.`as`\u003CString, String>(\"sink1\")\n        .withKeySerde(Serdes.String())\n        .withValueSerde(Serdes.String()),\n)\n","Kotlin","kotlin","",[324,465,466,483,489,511,562,591,616,623,635,644,673,689,704],{"__ignoreMap":463},[467,468,471,475,479],"span",{"class":469,"line":470},"line",1,[467,472,474],{"class":473},"sbBg2","val",[467,476,478],{"class":477},"suHK_"," intermediate ",[467,480,482],{"class":481},"sYZai","=\n",[467,484,486],{"class":469,"line":485},2,[467,487,488],{"class":477},"    stream1\n",[467,490,492,495,498,501,504,508],{"class":469,"line":491},3,[467,493,494],{"class":477},"        .",[467,496,366],{"class":497},"sySUi",[467,499,500],{"class":477}," { _, _ ",[467,502,503],{"class":481},"->",[467,505,507],{"class":506},"spphp"," \"lala\"",[467,509,510],{"class":477}," }\n",[467,512,514,516,519,522,526,529,532,535,537,539,542,544,548,550,553,556,559],{"class":469,"line":513},4,[467,515,494],{"class":477},[467,517,518],{"class":497},"map",[467,520,521],{"class":477}," { key, ",[467,523,525],{"class":524},"si04Y","value",[467,527,528],{"class":481}," ->",[467,530,531],{"class":497}," KeyValue",[467,533,534],{"class":477},"(",[467,536,525],{"class":524},[467,538,343],{"class":477},[467,540,541],{"class":497},"substring",[467,543,534],{"class":477},[467,545,547],{"class":546},"s-TwI","0",[467,549,409],{"class":477},[467,551,552],{"class":546},"3",[467,554,555],{"class":477},"), ",[467,557,558],{"class":506},"\"$key:$value\"",[467,560,561],{"class":477},") }\n",[467,563,565,567,570,573,575,577,580,583,586,589],{"class":469,"line":564},5,[467,566,494],{"class":477},[467,568,569],{"class":497},"filter",[467,571,572],{"class":477}," { _, ",[467,574,525],{"class":524},[467,576,528],{"class":481},[467,578,579],{"class":524}," value",[467,581,582],{"class":477},".length ",[467,584,585],{"class":524},">",[467,587,588],{"class":546}," 5",[467,590,510],{"class":477},[467,592,594,596,599,602,604,606,608,610,613],{"class":469,"line":593},6,[467,595,494],{"class":477},[467,597,598],{"class":497},"mapValues",[467,600,601],{"class":477}," { ",[467,603,525],{"class":524},[467,605,528],{"class":481},[467,607,579],{"class":524},[467,609,343],{"class":477},[467,611,612],{"class":497},"uppercase",[467,614,615],{"class":477},"() }\n",[467,617,619],{"class":469,"line":618},7,[467,620,622],{"emptyLinePlaceholder":621},true,"\n",[467,624,626,629,632],{"class":469,"line":625},8,[467,627,628],{"class":477},"intermediate.",[467,630,631],{"class":497},"to",[467,633,634],{"class":477},"(\n",[467,636,638,641],{"class":469,"line":637},9,[467,639,640],{"class":506},"    \"output-topic\"",[467,642,643],{"class":477},",\n",[467,645,647,650,653,656,660,662,664,667,670],{"class":469,"line":646},10,[467,648,649],{"class":477},"    Produced.",[467,651,652],{"class":497},"`as`",[467,654,655],{"class":477},"\u003C",[467,657,659],{"class":658},"sUxyF","String",[467,661,409],{"class":477},[467,663,659],{"class":658},[467,665,666],{"class":477},">(",[467,668,669],{"class":506},"\"sink1\"",[467,671,672],{"class":477},")\n",[467,674,676,678,681,684,686],{"class":469,"line":675},11,[467,677,494],{"class":477},[467,679,680],{"class":497},"withKeySerde",[467,682,683],{"class":477},"(Serdes.",[467,685,659],{"class":497},[467,687,688],{"class":477},"())\n",[467,690,692,694,697,699,701],{"class":469,"line":691},12,[467,693,494],{"class":477},[467,695,696],{"class":497},"withValueSerde",[467,698,683],{"class":477},[467,700,659],{"class":497},[467,702,703],{"class":477},"()),\n",[467,705,707],{"class":469,"line":706},13,[467,708,672],{"class":477},[457,710,715],{"className":711,"code":712,"filename":713,"language":714,"meta":463,"style":463},"language-java shiki shiki-themes vitesse-light","KStream\u003CString, String> intermediate =\n    stream1\n        .selectKey((k, v) -> \"lala\")\n        .map((key, value) -> KeyValue.pair(value.substring(0, 3), key + \":\" + value))\n        .filter((k, value) -> value.length() > 5)\n        .mapValues(value -> value.toUpperCase());\n\nintermediate.to(\n    \"output-topic\",\n    Produced.\u003CString, String>as(\"sink1\")\n        .withKeySerde(Serdes.String())\n        .withValueSerde(Serdes.String()));\n","Java","java",[324,716,717,740,744,778,845,880,903,907,918,930,961,978],{"__ignoreMap":463},[467,718,719,721,723,725,728,731,733,737],{"class":469,"line":470},[467,720,330],{"class":477},[467,722,655],{"class":481},[467,724,659],{"class":524},[467,726,727],{"class":481},",",[467,729,730],{"class":524}," String",[467,732,585],{"class":481},[467,734,736],{"class":735},"svycV"," intermediate",[467,738,739],{"class":481}," =\n",[467,741,742],{"class":469,"line":485},[467,743,488],{"class":477},[467,745,746,748,750,753,756,758,761,764,766,770,773,776],{"class":469,"line":491},[467,747,494],{"class":481},[467,749,366],{"class":497},[467,751,752],{"class":481},"((",[467,754,755],{"class":477},"k",[467,757,727],{"class":481},[467,759,760],{"class":477}," v",[467,762,763],{"class":481},")",[467,765,528],{"class":481},[467,767,769],{"class":768},"sSP4y"," \"",[467,771,772],{"class":506},"lala",[467,774,775],{"class":768},"\"",[467,777,672],{"class":481},[467,779,780,782,784,786,789,791,793,795,797,799,801,804,806,808,810,812,814,816,818,821,824,827,830,832,835,837,840,842],{"class":469,"line":513},[467,781,494],{"class":481},[467,783,518],{"class":497},[467,785,752],{"class":481},[467,787,788],{"class":477},"key",[467,790,727],{"class":481},[467,792,579],{"class":477},[467,794,763],{"class":481},[467,796,528],{"class":481},[467,798,531],{"class":735},[467,800,343],{"class":481},[467,802,803],{"class":497},"pair",[467,805,534],{"class":481},[467,807,525],{"class":735},[467,809,343],{"class":481},[467,811,541],{"class":497},[467,813,534],{"class":481},[467,815,547],{"class":546},[467,817,727],{"class":481},[467,819,820],{"class":546}," 3",[467,822,823],{"class":481},"),",[467,825,826],{"class":477}," key ",[467,828,829],{"class":524},"+",[467,831,769],{"class":768},[467,833,834],{"class":506},":",[467,836,775],{"class":768},[467,838,839],{"class":524}," +",[467,841,579],{"class":477},[467,843,844],{"class":481},"))\n",[467,846,847,849,851,853,855,857,859,861,863,865,867,870,873,876,878],{"class":469,"line":564},[467,848,494],{"class":481},[467,850,569],{"class":497},[467,852,752],{"class":481},[467,854,755],{"class":477},[467,856,727],{"class":481},[467,858,579],{"class":477},[467,860,763],{"class":481},[467,862,528],{"class":481},[467,864,579],{"class":735},[467,866,343],{"class":481},[467,868,869],{"class":497},"length",[467,871,872],{"class":481},"()",[467,874,875],{"class":524}," >",[467,877,588],{"class":546},[467,879,672],{"class":481},[467,881,882,884,886,888,891,893,895,897,900],{"class":469,"line":593},[467,883,494],{"class":481},[467,885,598],{"class":497},[467,887,534],{"class":481},[467,889,890],{"class":477},"value ",[467,892,503],{"class":481},[467,894,579],{"class":735},[467,896,343],{"class":481},[467,898,899],{"class":497},"toUpperCase",[467,901,902],{"class":481},"());\n",[467,904,905],{"class":469,"line":618},[467,906,622],{"emptyLinePlaceholder":621},[467,908,909,912,914,916],{"class":469,"line":625},[467,910,911],{"class":735},"intermediate",[467,913,343],{"class":481},[467,915,631],{"class":497},[467,917,634],{"class":481},[467,919,920,923,926,928],{"class":469,"line":637},[467,921,922],{"class":768},"    \"",[467,924,925],{"class":506},"output-topic",[467,927,775],{"class":768},[467,929,643],{"class":481},[467,931,932,935,937,939,941,943,945,947,950,952,954,957,959],{"class":469,"line":646},[467,933,934],{"class":477},"    Produced",[467,936,343],{"class":481},[467,938,655],{"class":524},[467,940,659],{"class":477},[467,942,727],{"class":481},[467,944,730],{"class":477},[467,946,585],{"class":524},[467,948,949],{"class":497},"as",[467,951,534],{"class":481},[467,953,775],{"class":768},[467,955,956],{"class":506},"sink1",[467,958,775],{"class":768},[467,960,672],{"class":481},[467,962,963,965,967,969,972,974,976],{"class":469,"line":675},[467,964,494],{"class":481},[467,966,680],{"class":497},[467,968,534],{"class":481},[467,970,971],{"class":735},"Serdes",[467,973,343],{"class":481},[467,975,659],{"class":497},[467,977,688],{"class":481},[467,979,980,982,984,986,988,990,992],{"class":469,"line":691},[467,981,494],{"class":481},[467,983,696],{"class":497},[467,985,534],{"class":481},[467,987,971],{"class":735},[467,989,343],{"class":481},[467,991,659],{"class":497},[467,993,994],{"class":481},"()));\n",[320,996,997,998,1001,1002,1005,1006,1009],{},"The imports differ — StoatFlow's DSL lives under ",[324,999,1000],{},"io.stoatflow.core.topology.*"," (and the runtime under ",[324,1003,1004],{},"io.stoatflow.runtime.*",") rather than ",[324,1007,1008],{},"org.apache.kafka.streams.*"," — and the entry point differs (covered below). The operators in between are the same shape, which is what makes a port mechanical rather than a rewrite.",[320,1011,1012,1013,1016,1017,1019,1020,1023,1024,1027,1028,1030,1031,343],{},"There are deliberate API-surface differences where the single-instance model makes a Kafka Streams concept unnecessary or replaces it with something Flink-shaped — for example, event-time extraction is expressed as a Flink-style ",[324,1014,1015],{},"WatermarkStrategy"," on ",[324,1018,420],{}," (the KS ",[324,1021,1022],{},"TimestampExtractor"," is still accepted and adapts onto one), and the partitioner settings on ",[324,1025,1026],{},"TableJoined"," are accepted as no-ops because there are no partition-bound tasks to route between. Those are catalogued in the ",[337,1029,301],{"href":302},"; everything below is the conceptual ",[1032,1033,1034],"em",{},"why",[396,1036,1038],{"id":1037},"single-instance-vs-multi-instance-and-rebalancing","Single instance vs. multi-instance and rebalancing",[320,1040,1041],{},"This is the root difference; the rest follow from it.",[320,1043,1044,1047,1048,1051],{},[354,1045,1046],{},"Kafka Streams"," is a cluster. You run N instances, each with some number of stream threads. The instances form a Kafka consumer group, the group coordinator assigns tasks (each bound to an input partition) across the live members, and that assignment changes — ",[1032,1049,1050],{},"rebalances"," — whenever an instance joins, leaves, or fails. Rebalancing is the mechanism that gives Kafka Streams its horizontal scaling and its failover, and it's also the source of much of its operational complexity: state has to migrate or restore on the new owner, processing pauses during the handoff, and you tune around it (standby replicas, static membership, cooperative rebalancing).",[320,1053,1054,1057,1058,1061,1062,1065],{},[354,1055,1056],{},"StoatFlow"," runs as exactly one active JVM process, assigned every partition of every source topic, with no group to rebalance. High availability comes from fast restart by default, with an opt-in ",[337,1059,1060],{"href":234},"hot-standby"," cluster (one active + warm standbys) for near-instant failover — still a single ",[1032,1063,1064],{},"active"," instance, never a second active taking over a partition (ADR-001).",[320,1067,1068,1069,1071,1072,1075,1076,1078,1079,1083,1084,343],{},"The trade-off is explicit: you give up open-ended horizontal scale-out and instead scale a single instance vertically with cores and memory. You cannot run two ",[1032,1070,1064],{}," replicas of the same StoatFlow application against the same source topics; the supported way to run more than one instance is the ",[1032,1073,1074],{},"passive"," ",[337,1077,1060],{"href":234}," cluster, which coordinates a single active explicitly. The reasoning behind accepting that trade is on ",[337,1080,1082],{"href":1081},"\u002Fproduct\u002Fmotivation","Motivation","; the consequences for operations are on ",[337,1085,47],{"href":1086},"\u002Fdocs\u002Fconcepts\u002Farchitecture#the-single-instance-model",[396,1088,1090],{"id":1089},"lane-parallelism-vs-partition-bound-tasks","Lane parallelism vs. partition-bound tasks",[320,1092,1093],{},"Because there's no cluster, parallelism is expressed differently.",[320,1095,1096,1097,1099],{},"In ",[354,1098,1046],{},", processing parallelism is capped by the partition count of the input topics: one task per partition, one stream thread runs one or more tasks. To process more in parallel you add partitions — a topic-level change with downstream consequences for every consumer of that topic. The unit of concurrency is the partition, and it's fixed by your topic layout.",[320,1101,1096,1102,1104,1105,1108,1109,1112],{},[354,1103,1056],{},", the single consumer reads ",[354,1106,1107],{},"all"," partitions, and the engine then distributes work across ",[354,1110,1111],{},"lanes"," — key-affinity units of concurrent processing inside the JVM. The same key always routes to the same lane (so per-key order is preserved); different keys run on different lanes in parallel. Lane count is decoupled from partition count: you set it at startup, and it scales with CPU cores, not with how many partitions the source topic happens to have. Lanes run on virtual threads, so a lane blocked on a REST call or a database query parks at near-zero cost while other lanes make progress — which makes in-line blocking enrichment natural in a way the partition-bound model isn't.",[320,1114,1115,1116,343],{},"The full treatment — choosing a lane count, why your keyspace caps the benefit, and the blocking-I\u002FO story — is on ",[337,1117,57],{"href":58},[396,1119,1121],{"id":1120},"in-memory-re-keying-vs-repartition-topics","In-memory re-keying vs. repartition topics",[320,1123,1124,1125,409,1127,1129],{},"When a topology changes a record's key — ",[324,1126,366],{},[324,1128,369],{},", or a key-changing join — the record may need to move to a different unit of parallelism.",[320,1131,1132,1134,1135,1138,1139,1142],{},[354,1133,1046],{}," handles this by writing the re-keyed record to an internal ",[354,1136,1137],{},"repartition topic"," and re-reading it on the other side, so that the new key lands on the correct partition (and therefore the correct task). That's a broker round-trip plus an extra serialize\u002Fdeserialize per re-keyed record. You can see it in a Kafka Streams topology as an explicit or implicit ",[324,1140,1141],{},"repartition()"," boundary:",[457,1144,1146],{"className":459,"code":1145,"language":462,"meta":463,"style":463},"\u002F\u002F Kafka Streams — re-keying forces a repartition topic round-trip\nstream1\n    .selectKey { _, _ -> \"lala\" }\n    .map { key, value -> KeyValue(value.substring(0, 3), \"$key:$value\") }\n    .repartition()          \u002F\u002F ← writes to an internal topic, re-reads on the other side\n    .filter { _, value -> value.length > 5 }\n",[324,1147,1148,1154,1159,1174,1210,1223],{"__ignoreMap":463},[467,1149,1150],{"class":469,"line":470},[467,1151,1153],{"class":1152},"s8zF2","\u002F\u002F Kafka Streams — re-keying forces a repartition topic round-trip\n",[467,1155,1156],{"class":469,"line":485},[467,1157,1158],{"class":477},"stream1\n",[467,1160,1161,1164,1166,1168,1170,1172],{"class":469,"line":491},[467,1162,1163],{"class":477},"    .",[467,1165,366],{"class":497},[467,1167,500],{"class":477},[467,1169,503],{"class":481},[467,1171,507],{"class":506},[467,1173,510],{"class":477},[467,1175,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208],{"class":469,"line":513},[467,1177,1163],{"class":477},[467,1179,518],{"class":497},[467,1181,521],{"class":477},[467,1183,525],{"class":524},[467,1185,528],{"class":481},[467,1187,531],{"class":497},[467,1189,534],{"class":477},[467,1191,525],{"class":524},[467,1193,343],{"class":477},[467,1195,541],{"class":497},[467,1197,534],{"class":477},[467,1199,547],{"class":546},[467,1201,409],{"class":477},[467,1203,552],{"class":546},[467,1205,555],{"class":477},[467,1207,558],{"class":506},[467,1209,561],{"class":477},[467,1211,1212,1214,1217,1220],{"class":469,"line":564},[467,1213,1163],{"class":477},[467,1215,1216],{"class":497},"repartition",[467,1218,1219],{"class":477},"()          ",[467,1221,1222],{"class":1152},"\u002F\u002F ← writes to an internal topic, re-reads on the other side\n",[467,1224,1225,1227,1229,1231,1233,1235,1237,1239,1241,1243],{"class":469,"line":593},[467,1226,1163],{"class":477},[467,1228,569],{"class":497},[467,1230,572],{"class":477},[467,1232,525],{"class":524},[467,1234,528],{"class":481},[467,1236,579],{"class":524},[467,1238,582],{"class":477},[467,1240,585],{"class":524},[467,1242,588],{"class":546},[467,1244,510],{"class":477},[320,1246,1247,1249,1250,1253,1254,331,1256,1258,1259,1261],{},[354,1248,1056],{}," re-hashes the new key and hands the record to the lane that owns it ",[354,1251,1252],{},"in-memory",", between lanes in the same process. There is no internal repartition topic, no broker hop, and no extra serialization round-trip — the same ",[324,1255,366],{},[324,1257,518],{}," chain needs no ",[324,1260,1141],{}," call at all:",[453,1263,1264,1350],{"group":455},[457,1265,1267],{"className":459,"code":1266,"filename":461,"language":462,"meta":463,"style":463},"\u002F\u002F StoatFlow — re-keying is an in-process handoff; no repartition() needed\nstream1\n    .selectKey { _, _ -> \"lala\" }\n    .map { key, value -> KeyValue(value.substring(0, 3), \"$key:$value\") }\n    .filter { _, value -> value.length > 5 }\n",[324,1268,1269,1274,1278,1292,1328],{"__ignoreMap":463},[467,1270,1271],{"class":469,"line":470},[467,1272,1273],{"class":1152},"\u002F\u002F StoatFlow — re-keying is an in-process handoff; no repartition() needed\n",[467,1275,1276],{"class":469,"line":485},[467,1277,1158],{"class":477},[467,1279,1280,1282,1284,1286,1288,1290],{"class":469,"line":491},[467,1281,1163],{"class":477},[467,1283,366],{"class":497},[467,1285,500],{"class":477},[467,1287,503],{"class":481},[467,1289,507],{"class":506},[467,1291,510],{"class":477},[467,1293,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326],{"class":469,"line":513},[467,1295,1163],{"class":477},[467,1297,518],{"class":497},[467,1299,521],{"class":477},[467,1301,525],{"class":524},[467,1303,528],{"class":481},[467,1305,531],{"class":497},[467,1307,534],{"class":477},[467,1309,525],{"class":524},[467,1311,343],{"class":477},[467,1313,541],{"class":497},[467,1315,534],{"class":477},[467,1317,547],{"class":546},[467,1319,409],{"class":477},[467,1321,552],{"class":546},[467,1323,555],{"class":477},[467,1325,558],{"class":506},[467,1327,561],{"class":477},[467,1329,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348],{"class":469,"line":564},[467,1331,1163],{"class":477},[467,1333,569],{"class":497},[467,1335,572],{"class":477},[467,1337,525],{"class":524},[467,1339,528],{"class":481},[467,1341,579],{"class":524},[467,1343,582],{"class":477},[467,1345,585],{"class":524},[467,1347,588],{"class":546},[467,1349,510],{"class":477},[457,1351,1353],{"className":711,"code":1352,"filename":713,"language":714,"meta":463,"style":463},"\u002F\u002F StoatFlow — re-keying is an in-process handoff; no repartition() needed\nstream1\n    .selectKey((k, v) -> \"lala\")\n    .map((key, value) -> KeyValue.pair(value.substring(0, 3), key + \":\" + value))\n    .filter((k, value) -> value.length() > 5);\n",[324,1354,1355,1359,1363,1389,1447],{"__ignoreMap":463},[467,1356,1357],{"class":469,"line":470},[467,1358,1273],{"class":1152},[467,1360,1361],{"class":469,"line":485},[467,1362,1158],{"class":477},[467,1364,1365,1367,1369,1371,1373,1375,1377,1379,1381,1383,1385,1387],{"class":469,"line":491},[467,1366,1163],{"class":481},[467,1368,366],{"class":497},[467,1370,752],{"class":481},[467,1372,755],{"class":477},[467,1374,727],{"class":481},[467,1376,760],{"class":477},[467,1378,763],{"class":481},[467,1380,528],{"class":481},[467,1382,769],{"class":768},[467,1384,772],{"class":506},[467,1386,775],{"class":768},[467,1388,672],{"class":481},[467,1390,1391,1393,1395,1397,1399,1401,1403,1405,1407,1409,1411,1413,1415,1417,1419,1421,1423,1425,1427,1429,1431,1433,1435,1437,1439,1441,1443,1445],{"class":469,"line":513},[467,1392,1163],{"class":481},[467,1394,518],{"class":497},[467,1396,752],{"class":481},[467,1398,788],{"class":477},[467,1400,727],{"class":481},[467,1402,579],{"class":477},[467,1404,763],{"class":481},[467,1406,528],{"class":481},[467,1408,531],{"class":735},[467,1410,343],{"class":481},[467,1412,803],{"class":497},[467,1414,534],{"class":481},[467,1416,525],{"class":735},[467,1418,343],{"class":481},[467,1420,541],{"class":497},[467,1422,534],{"class":481},[467,1424,547],{"class":546},[467,1426,727],{"class":481},[467,1428,820],{"class":546},[467,1430,823],{"class":481},[467,1432,826],{"class":477},[467,1434,829],{"class":524},[467,1436,769],{"class":768},[467,1438,834],{"class":506},[467,1440,775],{"class":768},[467,1442,839],{"class":524},[467,1444,579],{"class":477},[467,1446,844],{"class":481},[467,1448,1449,1451,1453,1455,1457,1459,1461,1463,1465,1467,1469,1471,1473,1475,1477],{"class":469,"line":564},[467,1450,1163],{"class":481},[467,1452,569],{"class":497},[467,1454,752],{"class":481},[467,1456,755],{"class":477},[467,1458,727],{"class":481},[467,1460,579],{"class":477},[467,1462,763],{"class":481},[467,1464,528],{"class":481},[467,1466,579],{"class":735},[467,1468,343],{"class":481},[467,1470,869],{"class":497},[467,1472,872],{"class":481},[467,1474,875],{"class":524},[467,1476,588],{"class":546},[467,1478,1479],{"class":481},");\n",[320,1481,1482,1483,1485,1486,343],{},"The output is identical to what Kafka Streams produces; the path is shorter (ADR-010). ",[324,1484,1141],{}," still exists in StoatFlow's DSL for source compatibility, but it's an in-memory operation rather than a topic round-trip. After any re-key, the new key's lane assignment again guarantees per-(new-)key ordering — the affinity property travels with the record across the handoff. The mechanism is detailed on ",[337,1487,57],{"href":1488},"\u002Fdocs\u002Fconcepts\u002Flanes-and-parallelism#in-memory-re-keying-instead-of-repartition-topics",[396,1490,1492],{"id":1491},"barrier-based-exactly-once-vs-per-task-transactions","Barrier-based exactly-once vs. per-task transactions",[320,1494,1495,1496,1499],{},"Both systems deliver exactly-once over Kafka transactions; the ",[354,1497,1498],{},"scope"," of the transaction is what differs.",[320,1501,1502,1504],{},[354,1503,1046],{}," coordinates exactly-once across the cluster. Each task commits its own work, and the framework manages the transactional producers and offset commits across all the tasks and instances participating — a distributed coordination problem, and historically a fiddly one to configure correctly.",[320,1506,1507,1509,1510,1513,1514,343],{},[354,1508,1056],{}," uses a single ",[354,1511,1512],{},"commit barrier"," that flows through the entire topology. When the barrier completes, the runtime executes one Kafka transaction that atomically commits every state-store write (via changelog topics), every sink output record, and the consumer-group offsets for every contributing input partition — all three together, or none. Because there's one process, there's one barrier and one transaction covering the whole topology: no per-task transactions to coordinate, no cross-instance two-phase commit, no external checkpoint store. Exactly-once is the default rather than something you opt into and tune (ADR-004). The protocol is in the Chandy-Lamport family of distributed-snapshot algorithms — see ",[337,1515,1517],{"href":1516},"\u002Fdocs\u002Fconcepts\u002Fexactly-once#the-commit-barrier","Exactly-once",[320,1519,1520,1521,1523],{},"The full mechanism, what \"exactly-once\" means in concrete terms here, crash recovery, and the at-least-once trade-off are on ",[337,1522,52],{"href":53},". The barrier scheduling cadence and the transaction protocol itself are implementation concerns and stay in the source.",[396,1525,1527],{"id":1526},"global-state-vs-partition-scoped-state","Global state vs. partition-scoped state",[320,1529,1530],{},"State placement follows directly from the single-instance model.",[320,1532,1096,1533,1535,1536,1539,1540,1542,1543,1545,1546,1549],{},[354,1534,1046],{},", state is ",[354,1537,1538],{},"partition-scoped",": each task owns the state for its partitions, in a local store, and a key is only reachable from the task that owns it. This is why ",[324,1541,334],{},"-",[324,1544,334],{}," joins require ",[354,1547,1548],{},"co-partitioning"," — both tables must be partitioned the same way so that matching keys land in the same task — and why interactive queries across a cluster need partition routing or RPC to reach the instance that holds a given key.",[320,1551,1096,1552,1535,1554,1557],{},[354,1553,1056],{},[354,1555,1556],{},"global",": every store lives in the one JVM, and any processing lane can read or write any key. There's no partition-scoped isolation, no inter-instance lookup protocol, and no replication of the same data across JVMs. Two consequences fall out:",[348,1559,1560,1570],{},[351,1561,1562,1075,1565,1542,1567,1569],{},[354,1563,1564],{},"No co-partitioning requirement.",[324,1566,334],{},[324,1568,334],{}," joins work without aligning the tables' partitioning, because there are no partition-bound tasks to align (ADR-007). Foreign-key joins likewise don't need a co-partitioned subscription topic.",[351,1571,1572,1575,1576,1579],{},[354,1573,1574],{},"No partition routing for queries."," Interactive Queries reach any store directly — there's no ",[324,1577,1578],{},"withPartition(...)"," and no cross-instance RPC, because all state is locally accessible (ADR-025).",[320,1581,1582,1583,343],{},"Correctness under concurrency is preserved by key affinity, not by partition isolation: each key is only ever touched by the single lane that owns it, so updates to one key are serialized while different keys update in parallel. The depth — why that's safe, the one cross-key case where you coordinate yourself, and the store types available — is on ",[337,1584,1585],{"href":63},"State and thread safety",[396,1587,1589],{"id":1588},"the-entry-point-and-the-deployment-unit","The entry point and the deployment unit",[320,1591,1592],{},"Two practical differences you hit immediately when porting.",[320,1594,1595,1598,1599,1602,1603,1606,1607,1610,1611,1614],{},[354,1596,1597],{},"Entry point."," Where Kafka Streams takes a ",[324,1600,1601],{},"Topology"," plus a ",[324,1604,1605],{},"Properties"," and a ",[324,1608,1609],{},"KafkaStreams"," object you ",[324,1612,1613],{},"start()",", StoatFlow has two front doors:",[348,1616,1617,1639],{},[351,1618,1619,1622,1623,1626,1627,1630,1631,331,1633,331,1636,343],{},[324,1620,1621],{},"StoatFlow.fromBuilder(config, builder)"," on the ",[324,1624,1625],{},":core"," module — the DSL-and-engine entry point, configured with a typed ",[324,1628,1629],{},"StreamsConfig",", with ",[324,1632,1613],{},[324,1634,1635],{},"awaitTermination()",[324,1637,1638],{},"close()",[351,1640,1641,1622,1644,1647,1648,1651,1652,1654],{},[324,1642,1643],{},"StoatFlowRuntime.fromConfig(...)",[324,1645,1646],{},":runtime"," module — the batteries-included wrapper that loads ",[324,1649,1650],{},"application.yaml",", starts the HTTP admin + metrics server and health endpoints, and runs your topology until terminated. This is what ",[337,1653,28],{"href":29}," uses.",[320,1656,1657,1660,1661,1663,1664,343],{},[354,1658,1659],{},"Deployment unit."," A Kafka Streams deployment is a fleet you size by instance count and partition count. A StoatFlow deployment is a single pod you size by cores and memory — one active replica, scaled vertically. High availability is a fast, clean restart by default, or an opt-in ",[337,1662,1060],{"href":234}," cluster where a warm passive standby takes over in seconds. The operational surface for that — health probes, metrics, the debug endpoints, and the failure policies — is described on ",[337,1665,47],{"href":1666},"\u002Fdocs\u002Fconcepts\u002Farchitecture#failure-modes-and-observability",[396,1668,1670],{"id":1669},"the-deltas-at-a-glance","The deltas at a glance",[1672,1673,1674,1688],"table",{},[1675,1676,1677],"thead",{},[1678,1679,1680,1684,1686],"tr",{},[1681,1682,1683],"th",{},"Aspect",[1681,1685,1046],{},[1681,1687,1056],{},[1689,1690,1691,1703,1714,1725,1736,1746,1757,1768,1779,1790,1801,1818],"tbody",{},[1678,1692,1693,1697,1700],{},[1694,1695,1696],"td",{},"Deployment",[1694,1698,1699],{},"Multiple instances, rebalancing consumer group",[1694,1701,1702],{},"Single instance, no group to rebalance",[1678,1704,1705,1708,1711],{},[1694,1706,1707],{},"Scaling",[1694,1709,1710],{},"Horizontal — add instances \u002F partitions",[1694,1712,1713],{},"Vertical — add cores \u002F memory",[1678,1715,1716,1719,1722],{},[1694,1717,1718],{},"Parallelism unit",[1694,1720,1721],{},"Task per input partition",[1694,1723,1724],{},"Key-affinity lane, decoupled from partitions",[1678,1726,1727,1730,1733],{},[1694,1728,1729],{},"Re-keying",[1694,1731,1732],{},"Internal repartition topic (broker round-trip)",[1694,1734,1735],{},"In-memory handoff between lanes",[1678,1737,1738,1740,1743],{},[1694,1739,1517],{},[1694,1741,1742],{},"Per-task transactions, coordinated across cluster",[1694,1744,1745],{},"One commit barrier → one transaction, whole topology",[1678,1747,1748,1751,1754],{},[1694,1749,1750],{},"Default guarantee",[1694,1752,1753],{},"Opt-in and tuned",[1694,1755,1756],{},"Exactly-once by default",[1678,1758,1759,1762,1765],{},[1694,1760,1761],{},"State model",[1694,1763,1764],{},"Partition-scoped, local to a task",[1694,1766,1767],{},"Global, reachable from any lane",[1678,1769,1770,1773,1776],{},[1694,1771,1772],{},"Table joins",[1694,1774,1775],{},"Require co-partitioning",[1694,1777,1778],{},"No co-partitioning needed",[1678,1780,1781,1784,1787],{},[1694,1782,1783],{},"Interactive Queries",[1694,1785,1786],{},"Partition routing \u002F RPC across instances",[1694,1788,1789],{},"Direct — all state is local",[1678,1791,1792,1795,1798],{},[1694,1793,1794],{},"Blocking I\u002FO",[1694,1796,1797],{},"Blocks a stream thread",[1694,1799,1800],{},"Parks a virtual thread cheaply",[1678,1802,1803,1806,1811],{},[1694,1804,1805],{},"Entry point",[1694,1807,1808],{},[324,1809,1810],{},"KafkaStreams(topology, props)",[1694,1812,1813,331,1816],{},[324,1814,1815],{},"StoatFlow.fromBuilder(...)",[324,1817,1643],{},[1678,1819,1820,1823,1826],{},[1694,1821,1822],{},"DSL",[1694,1824,1825],{},"Kafka Streams DSL",[1694,1827,1828],{},"The same DSL",[320,1830,1831,1832,343],{},"The full method-by-method parity table — including the handful of KS APIs that StoatFlow deliberately drops or replaces — is the ",[337,1833,301],{"href":302},[396,1835,1837],{"id":1836},"where-to-go-next","Where to go next",[348,1839,1840,1845,1850,1855,1860,1867],{},[351,1841,1842,1844],{},[337,1843,47],{"href":48}," — the single-instance model the deltas above all follow from",[351,1846,1847,1849],{},[337,1848,57],{"href":58}," — lanes, key affinity, and in-memory re-keying in full",[351,1851,1852,1854],{},[337,1853,1517],{"href":53}," — the commit barrier and crash recovery",[351,1856,1857,1859],{},[337,1858,1585],{"href":63}," — the global state model and co-partitioning-free joins",[351,1861,1862,1866],{},[337,1863,1865],{"href":1864},"\u002Fproduct\u002Fcomparison-matrix","Comparison matrix"," — feature-by-feature against Kafka Streams and Flink",[351,1868,1869,1871],{},[337,1870,342],{"href":259}," — porting an existing Kafka Streams topology",[1873,1874,1875],"style",{},"html pre.shiki code .sbBg2, html code.shiki .sbBg2{--shiki-default:#1E754F}html pre.shiki code .suHK_, html code.shiki .suHK_{--shiki-default:#393A34}html pre.shiki code .sYZai, html code.shiki .sYZai{--shiki-default:#999999}html pre.shiki code .sySUi, html code.shiki .sySUi{--shiki-default:#59873A}html pre.shiki code .spphp, html code.shiki .spphp{--shiki-default:#B56959}html pre.shiki code .si04Y, html code.shiki .si04Y{--shiki-default:#AB5959}html pre.shiki code .s-TwI, html code.shiki .s-TwI{--shiki-default:#2F798A}html pre.shiki code .sUxyF, html code.shiki .sUxyF{--shiki-default:#2E8F82}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svycV, html code.shiki .svycV{--shiki-default:#B07D48}html pre.shiki code .sSP4y, html code.shiki .sSP4y{--shiki-default:#B5695977}html pre.shiki code .s8zF2, html code.shiki .s8zF2{--shiki-default:#A0ADA0}",{"title":463,"searchDepth":485,"depth":485,"links":1877},[1878,1879,1880,1881,1882,1883,1884,1885,1886],{"id":398,"depth":485,"text":399},{"id":1037,"depth":485,"text":1038},{"id":1089,"depth":485,"text":1090},{"id":1120,"depth":485,"text":1121},{"id":1491,"depth":485,"text":1492},{"id":1526,"depth":485,"text":1527},{"id":1588,"depth":485,"text":1589},{"id":1669,"depth":485,"text":1670},{"id":1836,"depth":485,"text":1837},"The conceptual deltas if you already know Kafka Streams — single instance instead of a rebalancing cluster, in-memory re-keying instead of repartition topics, barrier-based exactly-once, global state, and lanes instead of partition-bound tasks. The DSL carries over unchanged.","md",{},{"icon":85},"explanation",{"title":82,"description":1887},"z9YI-hxB5Bys1VP10UAvBywtvG_unQLdItwcSC1uIgM",[1895,1897],{"title":77,"path":78,"stem":79,"description":1896,"icon":80,"children":-1},"How StoatFlow classifies failures — deserialization, processing, and production — the skip \u002F fail \u002F dead-letter policies you choose, DLQ semantics, and the commit failure that ends in a restart.",{"title":92,"path":88,"stem":89,"description":1898,"icon":93,"children":-1},"How to define a StoatFlow topology — StreamsBuilder, the KStream\u002FKTable abstractions, and the fan-out rule that reuses a KStream reference for multiple branches.",1783713352615]