[{"data":1,"prerenderedAt":3749},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fmigration\u002Fwithout-data-migration":314,"\u002Fdocs\u002Fmigration\u002Fwithout-data-migration-surround":3744},[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":264,"body":316,"description":3737,"extension":3738,"meta":3739,"navigation":3740,"pageType":3741,"path":265,"seo":3742,"stem":266,"__hash__":3743},"docs\u002F1.docs\u002F7.migration\u002F2.without-data-migration.md",{"type":317,"value":318,"toc":3719},"minimark",[319,332,340,413,429,434,493,501,505,548,555,559,577,582,720,725,833,862,875,879,920,924,1106,1140,1181,1185,1198,1723,1736,2170,2186,2302,2311,2365,2416,2420,2423,2611,2636,2640,2658,2690,2694,2791,2822,2826,2969,2995,2999,3005,3013,3062,3088,3092,3095,3149,3155,3212,3215,3252,3260,3278,3282,3288,3650,3663,3667,3715],[320,321,322,323,327,328,331],"p",{},"This is the simplest way to move a topology from Kafka Streams to StoatFlow: you do ",[324,325,326],"strong",{},"not"," carry the existing Kafka Streams state across. You point a StoatFlow build at the same source topics under a ",[324,329,330],{},"new"," application id (a fresh consumer group), let the stateful operators rebuild their state — from the source data or from StoatFlow's own changelog topics, never from the Kafka Streams ones — and switch downstream traffic once the new app has caught up.",[320,333,334,335,339],{},"It suits most cases: stateless topologies (nothing to carry), topologies whose source topics are still fully retained, and any time you can tolerate a re-read of the input. If your state can only be reconstructed by replaying years of compacted input you can't afford to re-read, see ",[336,337,338],"a",{"href":270},"Migration with data migration"," instead.",[341,342,343],"tldr-panel",{},[344,345,346,369,384,398,407],"ul",{},[347,348,349,352,353,357,358,361,362,357,365,368],"li",{},[324,350,351],{},"Swap the dependency:"," ",[354,355,356],"code",{},"org.apache.kafka:kafka-streams"," → ",[354,359,360],{},"io.stoatflow:stoatflow-runtime","; imports ",[354,363,364],{},"org.apache.kafka.streams.*",[354,366,367],{},"io.stoatflow.core.topology.*",".",[347,370,371,352,374,357,377,380,381,368],{},[324,372,373],{},"Swap the entry point:",[354,375,376],{},"new KafkaStreams(topology, props).start()",[354,378,379],{},"StoatFlowRuntime.fromConfig(...).start()"," with ",[354,382,383],{},"application.yaml",[347,385,386,389,390,393,394,397],{},[324,387,388],{},"New application id"," = a fresh consumer group. Set ",[354,391,392],{},"auto.offset.reset"," to ",[354,395,396],{},"earliest"," so stateful operators rebuild from the start of the source.",[347,399,400,352,403,406],{},[324,401,402],{},"Remove:",[354,404,405],{},"num.stream.threads",", standby replicas, rebalancing \u002F static-membership tuning — there's no cluster to rebalance.",[347,408,409,412],{},[324,410,411],{},"Keep:"," the topology code (same DSL), topic names, serdes, and most Kafka client properties.",[414,415,418],"callout",{"color":416,"icon":417},"info","i-lucide-info",[320,419,420,421,425,426,428],{},"This page is the procedure. For ",[422,423,424],"em",{},"why"," the model is different — single instance, lanes instead of tasks, in-memory re-keying, barrier-based exactly-once — read ",[336,427,82],{"href":83}," first. The DSL itself carries over unchanged.",[430,431,433],"h2",{"id":432},"when-this-path-applies","When this path applies",[435,436,437,450],"table",{},[438,439,440],"thead",{},[441,442,443,447],"tr",{},[444,445,446],"th",{},"Your topology",[444,448,449],{},"This path works because",[451,452,453,473,481],"tbody",{},[441,454,455,470],{},[456,457,458,459,462,463,462,466,469],"td",{},"Stateless (",[354,460,461],{},"map"," \u002F ",[354,464,465],{},"filter",[354,467,468],{},"flatMap"," \u002F branching \u002F re-routing)",[456,471,472],{},"There is no state to carry — the cutover is purely dependency + config.",[441,474,475,478],{},[456,476,477],{},"Stateful, with source topics still fully retained",[456,479,480],{},"Stateful operators rebuild by re-reading the source from the beginning.",[441,482,483,486],{},[456,484,485],{},"Stateful, backed by compacted input or rebuildable aggregates",[456,487,488,489,492],{},"StoatFlow builds its ",[324,490,491],{},"own"," changelog topics as it processes; a one-time reprocess re-derives the state.",[320,494,495,496,498,499,368],{},"The case this path does ",[324,497,326],{}," cover: state you can only get by replaying source you no longer have (or can't afford to re-read). StoatFlow cannot restore from a Kafka Streams changelog topic — the changelog formats are not interchangeable. That scenario is ",[336,500,338],{"href":270},[430,502,504],{"id":503},"the-four-step-cutover","The four-step cutover",[506,507,508,519,536,542],"ol",{},[347,509,510,513,514,516,517,368],{},[324,511,512],{},"Swap the dependency and imports"," — ",[354,515,360],{},", DSL imports under ",[354,518,367],{},[347,520,521,513,524,357,527,530,531,357,534,368],{},[324,522,523],{},"Swap the entry point",[354,525,526],{},"KafkaStreams",[354,528,529],{},"StoatFlowRuntime",", ",[354,532,533],{},"Properties",[354,535,383],{},[347,537,538,541],{},[324,539,540],{},"Run the StoatFlow app alongside the old one"," under a fresh application id so it builds an independent consumer group and (for stateful topologies) its own state. Let it catch up to the live offset.",[347,543,544,547],{},[324,545,546],{},"Switch downstream traffic"," to the StoatFlow output, then retire the Kafka Streams app.",[320,549,550,551,554],{},"Steps 1 and 2 are the code change. Steps 3 and 4 are the operational cutover. The rest of this page walks each one, grounded in the ",[354,552,553],{},"map-filter"," example that ships in both flavours.",[430,556,558],{"id":557},"step-1-dependency-and-build","Step 1 — dependency and build",[320,560,561,562,565,566,569,570,572,573,576],{},"Drop the Kafka Streams dependency and add ",[354,563,564],{},"stoatflow-runtime"," (the batteries-included module — it transitively pulls in ",[354,567,568],{},"stoatflow-core","). If you've followed ",[336,571,18],{"href":19},", the private Maven repository and the ",[354,574,575],{},"io.stoatflow"," Gradle plugin are already wired up.",[320,578,579],{},[324,580,581],{},"Before — the Kafka Streams build:",[583,584,590],"pre",{"className":585,"code":586,"filename":587,"language":588,"meta":589,"style":589},"language-kotlin shiki shiki-themes vitesse-light","\u002F\u002F build.gradle.kts (Kafka Streams)\nplugins {\n    kotlin(\"jvm\")\n    application\n}\n\napplication {\n    mainClass.set(\"com.example.MainKt\")\n}\n\ndependencies {\n    implementation(\"org.apache.kafka:kafka-streams:4.1.1\")\n    runtimeOnly(\"ch.qos.logback:logback-classic\")\n}\n","Gradle","kotlin","",[354,591,592,601,612,628,634,640,647,655,671,676,681,689,702,715],{"__ignoreMap":589},[593,594,597],"span",{"class":595,"line":596},"line",1,[593,598,600],{"class":599},"s8zF2","\u002F\u002F build.gradle.kts (Kafka Streams)\n",[593,602,604,608],{"class":595,"line":603},2,[593,605,607],{"class":606},"sySUi","plugins",[593,609,611],{"class":610},"suHK_"," {\n",[593,613,615,618,621,625],{"class":595,"line":614},3,[593,616,617],{"class":606},"    kotlin",[593,619,620],{"class":610},"(",[593,622,624],{"class":623},"spphp","\"jvm\"",[593,626,627],{"class":610},")\n",[593,629,631],{"class":595,"line":630},4,[593,632,633],{"class":610},"    application\n",[593,635,637],{"class":595,"line":636},5,[593,638,639],{"class":610},"}\n",[593,641,643],{"class":595,"line":642},6,[593,644,646],{"emptyLinePlaceholder":645},true,"\n",[593,648,650,653],{"class":595,"line":649},7,[593,651,652],{"class":606},"application",[593,654,611],{"class":610},[593,656,658,661,664,666,669],{"class":595,"line":657},8,[593,659,660],{"class":610},"    mainClass.",[593,662,663],{"class":606},"set",[593,665,620],{"class":610},[593,667,668],{"class":623},"\"com.example.MainKt\"",[593,670,627],{"class":610},[593,672,674],{"class":595,"line":673},9,[593,675,639],{"class":610},[593,677,679],{"class":595,"line":678},10,[593,680,646],{"emptyLinePlaceholder":645},[593,682,684,687],{"class":595,"line":683},11,[593,685,686],{"class":606},"dependencies",[593,688,611],{"class":610},[593,690,692,695,697,700],{"class":595,"line":691},12,[593,693,694],{"class":606},"    implementation",[593,696,620],{"class":610},[593,698,699],{"class":623},"\"org.apache.kafka:kafka-streams:4.1.1\"",[593,701,627],{"class":610},[593,703,705,708,710,713],{"class":595,"line":704},13,[593,706,707],{"class":606},"    runtimeOnly",[593,709,620],{"class":610},[593,711,712],{"class":623},"\"ch.qos.logback:logback-classic\"",[593,714,627],{"class":610},[593,716,718],{"class":595,"line":717},14,[593,719,639],{"class":610},[320,721,722],{},[324,723,724],{},"After — the StoatFlow build:",[583,726,728],{"className":585,"code":727,"filename":587,"language":588,"meta":589,"style":589},"\u002F\u002F build.gradle.kts (StoatFlow)\nplugins {\n    kotlin(\"jvm\")\n    id(\"io.stoatflow\") version \"\u003Cstoatflow-version>\"\n}\n\nstoatflow {\n    mainClass.set(\"com.example.MainKt\")\n}\n\ndependencies {\n    implementation(\"io.stoatflow:stoatflow-runtime:\u003Cstoatflow-version>\")\n    runtimeOnly(\"ch.qos.logback:logback-classic\")\n}\n",[354,729,730,735,741,751,767,771,775,782,794,798,802,808,819,829],{"__ignoreMap":589},[593,731,732],{"class":595,"line":596},[593,733,734],{"class":599},"\u002F\u002F build.gradle.kts (StoatFlow)\n",[593,736,737,739],{"class":595,"line":603},[593,738,607],{"class":606},[593,740,611],{"class":610},[593,742,743,745,747,749],{"class":595,"line":614},[593,744,617],{"class":606},[593,746,620],{"class":610},[593,748,624],{"class":623},[593,750,627],{"class":610},[593,752,753,756,758,761,764],{"class":595,"line":630},[593,754,755],{"class":606},"    id",[593,757,620],{"class":610},[593,759,760],{"class":623},"\"io.stoatflow\"",[593,762,763],{"class":610},") version ",[593,765,766],{"class":623},"\"\u003Cstoatflow-version>\"\n",[593,768,769],{"class":595,"line":636},[593,770,639],{"class":610},[593,772,773],{"class":595,"line":642},[593,774,646],{"emptyLinePlaceholder":645},[593,776,777,780],{"class":595,"line":649},[593,778,779],{"class":606},"stoatflow",[593,781,611],{"class":610},[593,783,784,786,788,790,792],{"class":595,"line":657},[593,785,660],{"class":610},[593,787,663],{"class":606},[593,789,620],{"class":610},[593,791,668],{"class":623},[593,793,627],{"class":610},[593,795,796],{"class":595,"line":673},[593,797,639],{"class":610},[593,799,800],{"class":595,"line":678},[593,801,646],{"emptyLinePlaceholder":645},[593,803,804,806],{"class":595,"line":683},[593,805,686],{"class":606},[593,807,611],{"class":610},[593,809,810,812,814,817],{"class":595,"line":691},[593,811,694],{"class":606},[593,813,620],{"class":610},[593,815,816],{"class":623},"\"io.stoatflow:stoatflow-runtime:\u003Cstoatflow-version>\"",[593,818,627],{"class":610},[593,820,821,823,825,827],{"class":595,"line":704},[593,822,707],{"class":606},[593,824,620],{"class":610},[593,826,712],{"class":623},[593,828,627],{"class":610},[593,830,831],{"class":595,"line":717},[593,832,639],{"class":610},[320,834,835,836,839,840,843,844,846,847,850,851,854,855,858,859,861],{},"The current version is ",[837,838],"stoatflow-version",{}," — substitute it for the ",[354,841,842],{},"\u003Cstoatflow-version>"," placeholder. The ",[354,845,575],{}," Gradle plugin applies the JDK 25 toolchain, the ",[354,848,849],{},"--enable-preview"," and ",[354,852,853],{},"--enable-native-access=ALL-UNNAMED"," JVM flags, ",[354,856,857],{},"-XX:+UseG1GC",", and a runnable fat-jar — so you don't hand-maintain any of the run wiring. See ",[336,860,18],{"href":19}," for the Maven equivalent and the plugin-free option.",[414,863,865],{"color":864,"icon":80},"warning",[320,866,867,868,871,872,368],{},"StoatFlow requires ",[324,869,870],{},"JDK 25+"," at both compile and run time (it uses JDK preview features). If your Kafka Streams app runs on an older JDK, the toolchain bump is part of this migration. See ",[336,873,874],{"href":19},"Installation → Configure the JVM toolchain",[430,876,878],{"id":877},"step-2-imports-entry-point-and-config","Step 2 — imports, entry point, and config",[320,880,881,882,885,886,462,889,462,892,895,896,899,900,393,902,904,905,908,909,911,912,393,914,917,918,368],{},"The topology code stays the same shape — same ",[354,883,884],{},"StreamsBuilder",", same operators, same ",[354,887,888],{},"Consumed",[354,890,891],{},"Produced",[354,893,894],{},"Named",". Two things change: the ",[324,897,898],{},"imports"," move from ",[354,901,364],{},[354,903,367],{},", and the ",[324,906,907],{},"bootstrap"," moves from a ",[354,910,526],{}," instance + ",[354,913,533],{},[354,915,916],{},"StoatFlowRuntime.fromConfig(...)"," + ",[354,919,383],{},[921,922,923],"h3",{"id":898},"Imports",[435,925,926,936],{},[438,927,928],{},[441,929,930,933],{},[444,931,932],{},"Kafka Streams import",[444,934,935],{},"StoatFlow import",[451,937,938,950,962,974,986,998,1010,1022,1042,1060,1073,1092],{},[441,939,940,945],{},[456,941,942],{},[354,943,944],{},"org.apache.kafka.streams.StreamsBuilder",[456,946,947],{},[354,948,949],{},"io.stoatflow.core.topology.StreamsBuilder",[441,951,952,957],{},[456,953,954],{},[354,955,956],{},"org.apache.kafka.streams.kstream.KStream",[456,958,959],{},[354,960,961],{},"io.stoatflow.core.topology.KStream",[441,963,964,969],{},[456,965,966],{},[354,967,968],{},"org.apache.kafka.streams.kstream.Consumed",[456,970,971],{},[354,972,973],{},"io.stoatflow.core.topology.Consumed",[441,975,976,981],{},[456,977,978],{},[354,979,980],{},"org.apache.kafka.streams.kstream.Produced",[456,982,983],{},[354,984,985],{},"io.stoatflow.core.topology.Produced",[441,987,988,993],{},[456,989,990],{},[354,991,992],{},"org.apache.kafka.streams.kstream.Named",[456,994,995],{},[354,996,997],{},"io.stoatflow.core.topology.Named",[441,999,1000,1005],{},[456,1001,1002],{},[354,1003,1004],{},"org.apache.kafka.streams.kstream.Branched",[456,1006,1007],{},[354,1008,1009],{},"io.stoatflow.core.topology.Branched",[441,1011,1012,1017],{},[456,1013,1014],{},[354,1015,1016],{},"org.apache.kafka.streams.KeyValue",[456,1018,1019],{},[354,1020,1021],{},"io.stoatflow.core.state.KeyValue",[441,1023,1024,1037],{},[456,1025,1026,1029,1030,530,1033,1036],{},[354,1027,1028],{},"org.apache.kafka.streams.state.*"," (",[354,1031,1032],{},"Stores",[354,1034,1035],{},"KeyValueStore",", …)",[456,1038,1039],{},[354,1040,1041],{},"io.stoatflow.core.state.*",[441,1043,1044,1055],{},[456,1045,1046,1029,1049,530,1052,1036],{},[354,1047,1048],{},"org.apache.kafka.streams.processor.api.*",[354,1050,1051],{},"Processor",[354,1053,1054],{},"Record",[456,1056,1057],{},[354,1058,1059],{},"io.stoatflow.core.processor.*",[441,1061,1062,1068],{},[456,1063,1064,1067],{},[354,1065,1066],{},"org.apache.kafka.streams.errors.*"," (exception handlers)",[456,1069,1070],{},[354,1071,1072],{},"io.stoatflow.core.exception.*",[441,1074,1075,1080],{},[456,1076,1077],{},[354,1078,1079],{},"org.apache.kafka.streams.CloseOptions",[456,1081,1082,352,1085],{},[354,1083,1084],{},"io.stoatflow.core.CloseOptions",[422,1086,1087,1088,1091],{},"(only if you call ",[354,1089,1090],{},"close(CloseOptions)","; see the note below)",[441,1093,1094,1099],{},[456,1095,1096],{},[354,1097,1098],{},"org.apache.kafka.common.serialization.Serdes",[456,1100,1101,352,1103],{},[354,1102,1098],{},[422,1104,1105],{},"(unchanged — Kafka serdes are reused)",[320,1107,1108,1109,1112,1113,530,1116,1119,1120,850,1123,1029,1126,530,1129,530,1132,1135,1136,1139],{},"The Kafka ",[324,1110,1111],{},"serde"," classes (",[354,1114,1115],{},"Serdes.String()",[354,1117,1118],{},"Serdes.Long()",", your Avro\u002FProtobuf\u002FJSON serdes) come from the Kafka clients library and carry over verbatim — as does everything else under ",[354,1121,1122],{},"org.apache.kafka.common.*",[354,1124,1125],{},"org.apache.kafka.clients.*",[354,1127,1128],{},"Headers",[354,1130,1131],{},"ConsumerRecord",[354,1133,1134],{},"ProducerRecord",", …). Only the Kafka Streams types move namespaces. See the ",[336,1137,1138],{"href":302},"KS compatibility matrix"," for the method-by-method DSL parity.",[414,1141,1142],{"color":416,"icon":417},[320,1143,1144,352,1152,1155,1156,1159,1160,1029,1162,1165,1166,352,1169,1172,1173,1176,1177,368],{},[324,1145,1146,462,1149,1151],{},[354,1147,1148],{},"close(Duration)",[354,1150,1090],{}," carry over.",[354,1153,1154],{},"streams.close(Duration.ofSeconds(30))"," works unchanged; ",[354,1157,1158],{},"close(CloseOptions...)"," uses the current top-level KIP-1153 shape via ",[354,1161,1084],{},[354,1163,1164],{},"CloseOptions.timeout(d).withGroupMembershipOperation(...)","). If your app used the ",[324,1167,1168],{},"deprecated nested",[354,1170,1171],{},"KafkaStreams.CloseOptions"," (KIP-812, ",[354,1174,1175],{},"new CloseOptions().timeout(d).leaveGroup(true)","), rewrite that one line to the factories. See ",[336,1178,1180],{"href":1179},"\u002Fdocs\u002Freference\u002Fks-compatibility-matrix#lifecycle","Lifecycle",[921,1182,1184],{"id":1183},"entry-point","Entry point",[320,1186,1187,1188,1190,1191,1193,1194,1197],{},"In Kafka Streams you build a ",[354,1189,533],{},", construct ",[354,1192,526],{},", register a shutdown hook, and call ",[354,1195,1196],{},"start()",". The map-filter example does exactly that:",[1199,1200,1202,1416],"code-tabs",{"group":1201},"lang",[583,1203,1206],{"className":585,"code":1204,"filename":1205,"language":588,"meta":589,"style":589},"\u002F\u002F Before — Kafka Streams\nval props = Properties().apply {\n    put(StreamsConfig.APPLICATION_ID_CONFIG, \"map-filter-example-ks\")\n    put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, System.getenv(\"KAFKA_BOOTSTRAP_SERVERS\") ?: \"localhost:9092\")\n    put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String()::class.java)\n    put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String()::class.java)\n}\n\nval builder = StreamsBuilder()\nbuildTopology(builder)\n\nval streams = KafkaStreams(builder.build(), props)\nRuntime.getRuntime().addShutdownHook(Thread { streams.close() })\nstreams.start()\nThread.currentThread().join()\n","Kotlin",[354,1207,1208,1213,1237,1250,1273,1292,1307,1311,1315,1330,1338,1342,1363,1390,1400],{"__ignoreMap":589},[593,1209,1210],{"class":595,"line":596},[593,1211,1212],{"class":599},"\u002F\u002F Before — Kafka Streams\n",[593,1214,1215,1219,1222,1226,1229,1232,1235],{"class":595,"line":603},[593,1216,1218],{"class":1217},"sbBg2","val",[593,1220,1221],{"class":610}," props ",[593,1223,1225],{"class":1224},"sYZai","=",[593,1227,1228],{"class":606}," Properties",[593,1230,1231],{"class":610},"().",[593,1233,1234],{"class":606},"apply",[593,1236,611],{"class":610},[593,1238,1239,1242,1245,1248],{"class":595,"line":614},[593,1240,1241],{"class":606},"    put",[593,1243,1244],{"class":610},"(StreamsConfig.APPLICATION_ID_CONFIG, ",[593,1246,1247],{"class":623},"\"map-filter-example-ks\"",[593,1249,627],{"class":610},[593,1251,1252,1254,1257,1260,1262,1265,1268,1271],{"class":595,"line":630},[593,1253,1241],{"class":606},[593,1255,1256],{"class":610},"(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, System.",[593,1258,1259],{"class":606},"getenv",[593,1261,620],{"class":610},[593,1263,1264],{"class":623},"\"KAFKA_BOOTSTRAP_SERVERS\"",[593,1266,1267],{"class":610},") ?: ",[593,1269,1270],{"class":623},"\"localhost:9092\"",[593,1272,627],{"class":610},[593,1274,1275,1277,1280,1283,1286,1289],{"class":595,"line":636},[593,1276,1241],{"class":606},[593,1278,1279],{"class":610},"(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.",[593,1281,1282],{"class":606},"String",[593,1284,1285],{"class":610},"()::",[593,1287,1288],{"class":606},"class",[593,1290,1291],{"class":610},".java)\n",[593,1293,1294,1296,1299,1301,1303,1305],{"class":595,"line":642},[593,1295,1241],{"class":606},[593,1297,1298],{"class":610},"(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.",[593,1300,1282],{"class":606},[593,1302,1285],{"class":610},[593,1304,1288],{"class":606},[593,1306,1291],{"class":610},[593,1308,1309],{"class":595,"line":649},[593,1310,639],{"class":610},[593,1312,1313],{"class":595,"line":657},[593,1314,646],{"emptyLinePlaceholder":645},[593,1316,1317,1319,1322,1324,1327],{"class":595,"line":673},[593,1318,1218],{"class":1217},[593,1320,1321],{"class":610}," builder ",[593,1323,1225],{"class":1224},[593,1325,1326],{"class":606}," StreamsBuilder",[593,1328,1329],{"class":610},"()\n",[593,1331,1332,1335],{"class":595,"line":678},[593,1333,1334],{"class":606},"buildTopology",[593,1336,1337],{"class":610},"(builder)\n",[593,1339,1340],{"class":595,"line":683},[593,1341,646],{"emptyLinePlaceholder":645},[593,1343,1344,1346,1349,1351,1354,1357,1360],{"class":595,"line":691},[593,1345,1218],{"class":1217},[593,1347,1348],{"class":610}," streams ",[593,1350,1225],{"class":1224},[593,1352,1353],{"class":606}," KafkaStreams",[593,1355,1356],{"class":610},"(builder.",[593,1358,1359],{"class":606},"build",[593,1361,1362],{"class":610},"(), props)\n",[593,1364,1365,1368,1371,1373,1376,1378,1381,1384,1387],{"class":595,"line":704},[593,1366,1367],{"class":610},"Runtime.",[593,1369,1370],{"class":606},"getRuntime",[593,1372,1231],{"class":610},[593,1374,1375],{"class":606},"addShutdownHook",[593,1377,620],{"class":610},[593,1379,1380],{"class":606},"Thread",[593,1382,1383],{"class":610}," { streams.",[593,1385,1386],{"class":606},"close",[593,1388,1389],{"class":610},"() })\n",[593,1391,1392,1395,1398],{"class":595,"line":717},[593,1393,1394],{"class":610},"streams.",[593,1396,1397],{"class":606},"start",[593,1399,1329],{"class":610},[593,1401,1403,1406,1409,1411,1414],{"class":595,"line":1402},15,[593,1404,1405],{"class":610},"Thread.",[593,1407,1408],{"class":606},"currentThread",[593,1410,1231],{"class":610},[593,1412,1413],{"class":606},"join",[593,1415,1329],{"class":610},[583,1417,1422],{"className":1418,"code":1419,"filename":1420,"language":1421,"meta":589,"style":589},"language-java shiki shiki-themes vitesse-light","\u002F\u002F Before — Kafka Streams\nvar props = new Properties();\nprops.put(StreamsConfig.APPLICATION_ID_CONFIG, \"map-filter-example-ks\");\nprops.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,\n    System.getenv().getOrDefault(\"KAFKA_BOOTSTRAP_SERVERS\", \"localhost:9092\"));\nprops.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());\nprops.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());\n\nvar builder = new StreamsBuilder();\nbuildTopology(builder);\n\nvar streams = new KafkaStreams(builder.build(), props);\nRuntime.getRuntime().addShutdownHook(new Thread(streams::close));\nstreams.start();\nThread.currentThread().join();\n","Java","java",[354,1423,1424,1428,1449,1485,1505,1540,1574,1605,1609,1624,1635,1639,1667,1699,1709],{"__ignoreMap":589},[593,1425,1426],{"class":595,"line":596},[593,1427,1212],{"class":599},[593,1429,1430,1434,1438,1441,1444,1446],{"class":595,"line":603},[593,1431,1433],{"class":1432},"si04Y","var",[593,1435,1437],{"class":1436},"svycV"," props",[593,1439,1440],{"class":1224}," =",[593,1442,1443],{"class":1217}," new",[593,1445,1228],{"class":606},[593,1447,1448],{"class":1224},"();\n",[593,1450,1451,1454,1456,1459,1461,1464,1466,1469,1472,1476,1479,1482],{"class":595,"line":614},[593,1452,1453],{"class":1436},"props",[593,1455,368],{"class":1224},[593,1457,1458],{"class":606},"put",[593,1460,620],{"class":1224},[593,1462,1463],{"class":1436},"StreamsConfig",[593,1465,368],{"class":1224},[593,1467,1468],{"class":1436},"APPLICATION_ID_CONFIG",[593,1470,1471],{"class":1224},",",[593,1473,1475],{"class":1474},"sSP4y"," \"",[593,1477,1478],{"class":623},"map-filter-example-ks",[593,1480,1481],{"class":1474},"\"",[593,1483,1484],{"class":1224},");\n",[593,1486,1487,1489,1491,1493,1495,1497,1499,1502],{"class":595,"line":630},[593,1488,1453],{"class":1436},[593,1490,368],{"class":1224},[593,1492,1458],{"class":606},[593,1494,620],{"class":1224},[593,1496,1463],{"class":1436},[593,1498,368],{"class":1224},[593,1500,1501],{"class":1436},"BOOTSTRAP_SERVERS_CONFIG",[593,1503,1504],{"class":1224},",\n",[593,1506,1507,1510,1512,1514,1516,1519,1521,1523,1526,1528,1530,1532,1535,1537],{"class":595,"line":636},[593,1508,1509],{"class":1436},"    System",[593,1511,368],{"class":1224},[593,1513,1259],{"class":606},[593,1515,1231],{"class":1224},[593,1517,1518],{"class":606},"getOrDefault",[593,1520,620],{"class":1224},[593,1522,1481],{"class":1474},[593,1524,1525],{"class":623},"KAFKA_BOOTSTRAP_SERVERS",[593,1527,1481],{"class":1474},[593,1529,1471],{"class":1224},[593,1531,1475],{"class":1474},[593,1533,1534],{"class":623},"localhost:9092",[593,1536,1481],{"class":1474},[593,1538,1539],{"class":1224},"));\n",[593,1541,1542,1544,1546,1548,1550,1552,1554,1557,1559,1562,1564,1566,1568,1571],{"class":595,"line":642},[593,1543,1453],{"class":1436},[593,1545,368],{"class":1224},[593,1547,1458],{"class":606},[593,1549,620],{"class":1224},[593,1551,1463],{"class":1436},[593,1553,368],{"class":1224},[593,1555,1556],{"class":1436},"DEFAULT_KEY_SERDE_CLASS_CONFIG",[593,1558,1471],{"class":1224},[593,1560,1561],{"class":1436}," Serdes",[593,1563,368],{"class":1224},[593,1565,1282],{"class":606},[593,1567,1231],{"class":1224},[593,1569,1570],{"class":606},"getClass",[593,1572,1573],{"class":1224},"());\n",[593,1575,1576,1578,1580,1582,1584,1586,1588,1591,1593,1595,1597,1599,1601,1603],{"class":595,"line":649},[593,1577,1453],{"class":1436},[593,1579,368],{"class":1224},[593,1581,1458],{"class":606},[593,1583,620],{"class":1224},[593,1585,1463],{"class":1436},[593,1587,368],{"class":1224},[593,1589,1590],{"class":1436},"DEFAULT_VALUE_SERDE_CLASS_CONFIG",[593,1592,1471],{"class":1224},[593,1594,1561],{"class":1436},[593,1596,368],{"class":1224},[593,1598,1282],{"class":606},[593,1600,1231],{"class":1224},[593,1602,1570],{"class":606},[593,1604,1573],{"class":1224},[593,1606,1607],{"class":595,"line":657},[593,1608,646],{"emptyLinePlaceholder":645},[593,1610,1611,1613,1616,1618,1620,1622],{"class":595,"line":673},[593,1612,1433],{"class":1432},[593,1614,1615],{"class":1436}," builder",[593,1617,1440],{"class":1224},[593,1619,1443],{"class":1217},[593,1621,1326],{"class":606},[593,1623,1448],{"class":1224},[593,1625,1626,1628,1630,1633],{"class":595,"line":678},[593,1627,1334],{"class":606},[593,1629,620],{"class":1224},[593,1631,1632],{"class":610},"builder",[593,1634,1484],{"class":1224},[593,1636,1637],{"class":595,"line":683},[593,1638,646],{"emptyLinePlaceholder":645},[593,1640,1641,1643,1646,1648,1650,1652,1654,1656,1658,1660,1663,1665],{"class":595,"line":691},[593,1642,1433],{"class":1432},[593,1644,1645],{"class":1436}," streams",[593,1647,1440],{"class":1224},[593,1649,1443],{"class":1217},[593,1651,1353],{"class":606},[593,1653,620],{"class":1224},[593,1655,1632],{"class":1436},[593,1657,368],{"class":1224},[593,1659,1359],{"class":606},[593,1661,1662],{"class":1224},"(),",[593,1664,1437],{"class":610},[593,1666,1484],{"class":1224},[593,1668,1669,1672,1674,1676,1678,1680,1682,1684,1687,1689,1692,1695,1697],{"class":595,"line":704},[593,1670,1671],{"class":1436},"Runtime",[593,1673,368],{"class":1224},[593,1675,1370],{"class":606},[593,1677,1231],{"class":1224},[593,1679,1375],{"class":606},[593,1681,620],{"class":1224},[593,1683,330],{"class":1217},[593,1685,1686],{"class":606}," Thread",[593,1688,620],{"class":1224},[593,1690,1691],{"class":610},"streams",[593,1693,1694],{"class":1217},"::",[593,1696,1386],{"class":610},[593,1698,1539],{"class":1224},[593,1700,1701,1703,1705,1707],{"class":595,"line":717},[593,1702,1691],{"class":1436},[593,1704,368],{"class":1224},[593,1706,1397],{"class":606},[593,1708,1448],{"class":1224},[593,1710,1711,1713,1715,1717,1719,1721],{"class":595,"line":1402},[593,1712,1380],{"class":1436},[593,1714,368],{"class":1224},[593,1716,1408],{"class":606},[593,1718,1231],{"class":1224},[593,1720,1413],{"class":606},[593,1722,1448],{"class":1224},[320,1724,1725,1726,1728,1729,1731,1732,1735],{},"On StoatFlow, ",[354,1727,916],{}," loads ",[354,1730,383],{},", starts the HTTP + metrics server, installs graceful shutdown, and runs the topology until terminated. Non-serializable settings — the default serdes, exception handlers — are set in code via ",[354,1733,1734],{},"streamsConfigOverrides { ... }","; everything else lives in YAML:",[1199,1737,1738,1899],{"group":1201},[583,1739,1741],{"className":585,"code":1740,"filename":1205,"language":588,"meta":589,"style":589},"\u002F\u002F After — StoatFlow runtime\nimport io.stoatflow.core.topology.StreamsBuilder\nimport io.stoatflow.runtime.StoatFlowRuntime\nimport org.apache.kafka.common.serialization.Serdes\n\nfun main() {\n    val runtime = StoatFlowRuntime.fromConfig(\n        topologyBuilder = { buildTopology(it) },\n        configure = {\n            streamsConfigOverrides {\n                defaultKeySerde(Serdes.String())\n                defaultValueSerde(Serdes.String())\n            }\n        },\n    )\n    runtime.start()\n    runtime.awaitTermination()\n}\n",[354,1742,1743,1748,1756,1763,1770,1774,1785,1804,1819,1828,1835,1848,1859,1864,1869,1874,1884,1894],{"__ignoreMap":589},[593,1744,1745],{"class":595,"line":596},[593,1746,1747],{"class":599},"\u002F\u002F After — StoatFlow runtime\n",[593,1749,1750,1753],{"class":595,"line":603},[593,1751,1752],{"class":1217},"import",[593,1754,1755],{"class":606}," io.stoatflow.core.topology.StreamsBuilder\n",[593,1757,1758,1760],{"class":595,"line":614},[593,1759,1752],{"class":1217},[593,1761,1762],{"class":606}," io.stoatflow.runtime.StoatFlowRuntime\n",[593,1764,1765,1767],{"class":595,"line":630},[593,1766,1752],{"class":1217},[593,1768,1769],{"class":606}," org.apache.kafka.common.serialization.Serdes\n",[593,1771,1772],{"class":595,"line":636},[593,1773,646],{"emptyLinePlaceholder":645},[593,1775,1776,1779,1782],{"class":595,"line":642},[593,1777,1778],{"class":1217},"fun",[593,1780,1781],{"class":606}," main",[593,1783,1784],{"class":610},"() {\n",[593,1786,1787,1790,1793,1795,1798,1801],{"class":595,"line":649},[593,1788,1789],{"class":1217},"    val",[593,1791,1792],{"class":610}," runtime ",[593,1794,1225],{"class":1224},[593,1796,1797],{"class":610}," StoatFlowRuntime.",[593,1799,1800],{"class":606},"fromConfig",[593,1802,1803],{"class":610},"(\n",[593,1805,1806,1809,1811,1814,1816],{"class":595,"line":657},[593,1807,1808],{"class":610},"        topologyBuilder ",[593,1810,1225],{"class":1224},[593,1812,1813],{"class":610}," { ",[593,1815,1334],{"class":606},[593,1817,1818],{"class":610},"(it) },\n",[593,1820,1821,1824,1826],{"class":595,"line":673},[593,1822,1823],{"class":610},"        configure ",[593,1825,1225],{"class":1224},[593,1827,611],{"class":610},[593,1829,1830,1833],{"class":595,"line":678},[593,1831,1832],{"class":606},"            streamsConfigOverrides",[593,1834,611],{"class":610},[593,1836,1837,1840,1843,1845],{"class":595,"line":683},[593,1838,1839],{"class":606},"                defaultKeySerde",[593,1841,1842],{"class":610},"(Serdes.",[593,1844,1282],{"class":606},[593,1846,1847],{"class":610},"())\n",[593,1849,1850,1853,1855,1857],{"class":595,"line":691},[593,1851,1852],{"class":606},"                defaultValueSerde",[593,1854,1842],{"class":610},[593,1856,1282],{"class":606},[593,1858,1847],{"class":610},[593,1860,1861],{"class":595,"line":704},[593,1862,1863],{"class":610},"            }\n",[593,1865,1866],{"class":595,"line":717},[593,1867,1868],{"class":610},"        },\n",[593,1870,1871],{"class":595,"line":1402},[593,1872,1873],{"class":610},"    )\n",[593,1875,1877,1880,1882],{"class":595,"line":1876},16,[593,1878,1879],{"class":610},"    runtime.",[593,1881,1397],{"class":606},[593,1883,1329],{"class":610},[593,1885,1887,1889,1892],{"class":595,"line":1886},17,[593,1888,1879],{"class":610},[593,1890,1891],{"class":606},"awaitTermination",[593,1893,1329],{"class":610},[593,1895,1897],{"class":595,"line":1896},18,[593,1898,639],{"class":610},[583,1900,1902],{"className":1418,"code":1901,"filename":1420,"language":1421,"meta":589,"style":589},"\u002F\u002F After — StoatFlow runtime\nimport io.stoatflow.core.topology.StreamsBuilder;\nimport io.stoatflow.runtime.StoatFlowRuntime;\nimport org.apache.kafka.common.serialization.Serdes;\n\npublic class Main {\n    public static void main(String[] args) {\n        var runtime = StoatFlowRuntime.fromConfig(\n            Main::buildTopology,\n            builder -> builder.streamsConfigOverrides(cfg -> {\n                cfg.defaultKeySerde(Serdes.String());\n                cfg.defaultValueSerde(Serdes.String());\n            })\n        );\n        runtime.start();\n        runtime.awaitTermination();\n    }\n}\n",[354,1903,1904,1908,1936,1957,1991,1995,2009,2037,2056,2067,2091,2111,2130,2135,2140,2151,2161,2166],{"__ignoreMap":589},[593,1905,1906],{"class":595,"line":596},[593,1907,1747],{"class":599},[593,1909,1910,1912,1915,1917,1919,1921,1924,1926,1929,1931,1933],{"class":595,"line":603},[593,1911,1752],{"class":1217},[593,1913,1914],{"class":610}," io",[593,1916,368],{"class":1224},[593,1918,779],{"class":610},[593,1920,368],{"class":1224},[593,1922,1923],{"class":610},"core",[593,1925,368],{"class":1224},[593,1927,1928],{"class":610},"topology",[593,1930,368],{"class":1224},[593,1932,884],{"class":610},[593,1934,1935],{"class":1224},";\n",[593,1937,1938,1940,1942,1944,1946,1948,1951,1953,1955],{"class":595,"line":614},[593,1939,1752],{"class":1217},[593,1941,1914],{"class":610},[593,1943,368],{"class":1224},[593,1945,779],{"class":610},[593,1947,368],{"class":1224},[593,1949,1950],{"class":610},"runtime",[593,1952,368],{"class":1224},[593,1954,529],{"class":610},[593,1956,1935],{"class":1224},[593,1958,1959,1961,1964,1966,1969,1971,1974,1976,1979,1981,1984,1986,1989],{"class":595,"line":630},[593,1960,1752],{"class":1217},[593,1962,1963],{"class":610}," org",[593,1965,368],{"class":1224},[593,1967,1968],{"class":610},"apache",[593,1970,368],{"class":1224},[593,1972,1973],{"class":610},"kafka",[593,1975,368],{"class":1224},[593,1977,1978],{"class":610},"common",[593,1980,368],{"class":1224},[593,1982,1983],{"class":610},"serialization",[593,1985,368],{"class":1224},[593,1987,1988],{"class":610},"Serdes",[593,1990,1935],{"class":1224},[593,1992,1993],{"class":595,"line":636},[593,1994,646],{"emptyLinePlaceholder":645},[593,1996,1997,2000,2003,2007],{"class":595,"line":642},[593,1998,1999],{"class":1432},"public",[593,2001,2002],{"class":1432}," class",[593,2004,2006],{"class":2005},"sUxyF"," Main",[593,2008,611],{"class":1224},[593,2010,2011,2014,2017,2020,2022,2024,2026,2029,2032,2035],{"class":595,"line":649},[593,2012,2013],{"class":1432},"    public",[593,2015,2016],{"class":1432}," static",[593,2018,2019],{"class":1432}," void",[593,2021,1781],{"class":606},[593,2023,620],{"class":1224},[593,2025,1282],{"class":1432},[593,2027,2028],{"class":1224},"[]",[593,2030,2031],{"class":1436}," args",[593,2033,2034],{"class":1224},")",[593,2036,611],{"class":1224},[593,2038,2039,2042,2045,2047,2050,2052,2054],{"class":595,"line":657},[593,2040,2041],{"class":1432},"        var",[593,2043,2044],{"class":1436}," runtime",[593,2046,1440],{"class":1224},[593,2048,2049],{"class":1436}," StoatFlowRuntime",[593,2051,368],{"class":1224},[593,2053,1800],{"class":606},[593,2055,1803],{"class":1224},[593,2057,2058,2061,2063,2065],{"class":595,"line":673},[593,2059,2060],{"class":610},"            Main",[593,2062,1694],{"class":1217},[593,2064,1334],{"class":610},[593,2066,1504],{"class":1224},[593,2068,2069,2072,2075,2077,2079,2082,2084,2087,2089],{"class":595,"line":678},[593,2070,2071],{"class":610},"            builder ",[593,2073,2074],{"class":1224},"->",[593,2076,1615],{"class":1436},[593,2078,368],{"class":1224},[593,2080,2081],{"class":606},"streamsConfigOverrides",[593,2083,620],{"class":1224},[593,2085,2086],{"class":610},"cfg ",[593,2088,2074],{"class":1224},[593,2090,611],{"class":1224},[593,2092,2093,2096,2098,2101,2103,2105,2107,2109],{"class":595,"line":683},[593,2094,2095],{"class":1436},"                cfg",[593,2097,368],{"class":1224},[593,2099,2100],{"class":606},"defaultKeySerde",[593,2102,620],{"class":1224},[593,2104,1988],{"class":1436},[593,2106,368],{"class":1224},[593,2108,1282],{"class":606},[593,2110,1573],{"class":1224},[593,2112,2113,2115,2117,2120,2122,2124,2126,2128],{"class":595,"line":691},[593,2114,2095],{"class":1436},[593,2116,368],{"class":1224},[593,2118,2119],{"class":606},"defaultValueSerde",[593,2121,620],{"class":1224},[593,2123,1988],{"class":1436},[593,2125,368],{"class":1224},[593,2127,1282],{"class":606},[593,2129,1573],{"class":1224},[593,2131,2132],{"class":595,"line":704},[593,2133,2134],{"class":1224},"            })\n",[593,2136,2137],{"class":595,"line":717},[593,2138,2139],{"class":1224},"        );\n",[593,2141,2142,2145,2147,2149],{"class":595,"line":1402},[593,2143,2144],{"class":1436},"        runtime",[593,2146,368],{"class":1224},[593,2148,1397],{"class":606},[593,2150,1448],{"class":1224},[593,2152,2153,2155,2157,2159],{"class":595,"line":1876},[593,2154,2144],{"class":1436},[593,2156,368],{"class":1224},[593,2158,1891],{"class":606},[593,2160,1448],{"class":1224},[593,2162,2163],{"class":595,"line":1886},[593,2164,2165],{"class":1224},"    }\n",[593,2167,2168],{"class":595,"line":1896},[593,2169,639],{"class":1224},[320,2171,2172,2173,850,2176,2179,2180,2182,2183,2185],{},"The ",[354,2174,2175],{},"application-id",[354,2177,2178],{},"bootstrap-servers"," move out of ",[354,2181,533],{}," and into ",[354,2184,383],{},":",[583,2187,2191],{"className":2188,"code":2189,"language":2190,"meta":589,"style":589},"language-yaml shiki shiki-themes vitesse-light","# src\u002Fmain\u002Fresources\u002Fapplication.yaml\nstoatflow:\n  application-id: map-filter-stoatflow        # NEW id — a fresh consumer group\n  bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:-localhost:9092}\n\n  license:\n    key: ${STOATFLOW_LICENSE_KEY}\n\nruntime:\n  http:\n    enabled: true\n    port: ${HTTP_PORT:-8080}\n  metrics:\n    enabled: true\n","yaml",[354,2192,2193,2198,2206,2219,2229,2233,2240,2250,2254,2260,2267,2277,2287,2294],{"__ignoreMap":589},[593,2194,2195],{"class":595,"line":596},[593,2196,2197],{"class":599},"# src\u002Fmain\u002Fresources\u002Fapplication.yaml\n",[593,2199,2200,2203],{"class":595,"line":603},[593,2201,779],{"class":2202},"su6XF",[593,2204,2205],{"class":1224},":\n",[593,2207,2208,2211,2213,2216],{"class":595,"line":614},[593,2209,2210],{"class":2202},"  application-id",[593,2212,2185],{"class":1224},[593,2214,2215],{"class":623}," map-filter-stoatflow",[593,2217,2218],{"class":599},"        # NEW id — a fresh consumer group\n",[593,2220,2221,2224,2226],{"class":595,"line":630},[593,2222,2223],{"class":2202},"  bootstrap-servers",[593,2225,2185],{"class":1224},[593,2227,2228],{"class":623}," ${KAFKA_BOOTSTRAP_SERVERS:-localhost:9092}\n",[593,2230,2231],{"class":595,"line":636},[593,2232,646],{"emptyLinePlaceholder":645},[593,2234,2235,2238],{"class":595,"line":642},[593,2236,2237],{"class":2202},"  license",[593,2239,2205],{"class":1224},[593,2241,2242,2245,2247],{"class":595,"line":649},[593,2243,2244],{"class":2202},"    key",[593,2246,2185],{"class":1224},[593,2248,2249],{"class":623}," ${STOATFLOW_LICENSE_KEY}\n",[593,2251,2252],{"class":595,"line":657},[593,2253,646],{"emptyLinePlaceholder":645},[593,2255,2256,2258],{"class":595,"line":673},[593,2257,1950],{"class":2202},[593,2259,2205],{"class":1224},[593,2261,2262,2265],{"class":595,"line":678},[593,2263,2264],{"class":2202},"  http",[593,2266,2205],{"class":1224},[593,2268,2269,2272,2274],{"class":595,"line":683},[593,2270,2271],{"class":2202},"    enabled",[593,2273,2185],{"class":1224},[593,2275,2276],{"class":1217}," true\n",[593,2278,2279,2282,2284],{"class":595,"line":691},[593,2280,2281],{"class":2202},"    port",[593,2283,2185],{"class":1224},[593,2285,2286],{"class":623}," ${HTTP_PORT:-8080}\n",[593,2288,2289,2292],{"class":595,"line":704},[593,2290,2291],{"class":2202},"  metrics",[593,2293,2205],{"class":1224},[593,2295,2296,2298,2300],{"class":595,"line":717},[593,2297,2271],{"class":2202},[593,2299,2185],{"class":1224},[593,2301,2276],{"class":1217},[320,2303,2304,2305,2308,2309,368],{},"StoatFlow is licensed — ",[354,2306,2307],{},"stoatflow.license.key"," is required and has no Kafka Streams equivalent. See ",[336,2310,23],{"href":24},[414,2312,2313],{"color":416,"icon":198},[320,2314,2315,2321,2322,2324,2325,2328,2329,530,2332,2335,2336,2339,2340,2343,2344,2347,2348,2351,2352,2354,2355,462,2358,2361,2362,2364],{},[324,2316,2317,2318,368],{},"Porting a ",[354,2319,2320],{},"setStateListener"," A Kafka Streams ",[354,2323,2320],{}," keyed on ",[354,2326,2327],{},"KafkaStreams.State"," is usually wired for readiness or alerting. Under the runtime, that surface is the operational one — ",[354,2330,2331],{},"\u002Fhealth\u002Fready",[354,2333,2334],{},"\u002Fhealth\u002Flive",", and ",[354,2337,2338],{},"\u002Fstate"," (see ",[336,2341,2342],{"href":186},"REST API",") — so most such listeners retire. If you build against ",[354,2345,2346],{},":core"," directly, ",[354,2349,2350],{},"StoatFlow.setStateListener(...)"," is the KS-exact single overload (a method reference or lambda, no cast), and StoatFlow's richer 10-state lifecycle maps back onto the KS 7-state ",[354,2353,2327],{}," at the boundary via ",[354,2356,2357],{},"newState.toKafkaStreamsState()",[354,2359,2360],{},"app.kafkaStreamsState()",". See the ",[336,2363,1180],{"href":1179}," rows in the compatibility matrix.",[414,2366,2367],{"color":416,"icon":103},[320,2368,2369,2370,462,2373,462,2375,462,2377,462,2380,462,2383,462,2386,2389,2390,2393,2394,2397,2398,2400,2401,2404,2405,2407,2408,2411,2412,2415],{},"The operators that the two map-filter examples have in common — the ",[354,2371,2372],{},"selectKey",[354,2374,461],{},[354,2376,465],{},[354,2378,2379],{},"mapValues",[354,2381,2382],{},"peek",[354,2384,2385],{},"split",[354,2387,2388],{},"to"," chain — port across with only the import-namespace change. Compare the Kafka Streams example (",[354,2391,2392],{},"examples-ks\u002Fmap-filter",") against the StoatFlow runtime example (",[354,2395,2396],{},"examples\u002Fmap-filter-runtime","): the overlapping DSL is the same, line for line, aside from the namespace. The two examples are ",[422,2399,326],{}," identical — the StoatFlow one also exercises StoatFlow-only surface (a ",[354,2402,2403],{},"scheduled"," source, a ",[354,2406,1051],{}," with key-based timers, an extra ",[354,2409,2410],{},"table(...)"," branch), and the Kafka Streams one keeps an explicit ",[354,2413,2414],{},"repartition()"," that StoatFlow's in-memory re-keying makes unnecessary. But that's added scope, not a rewrite of the shared chain. That's the point of the DSL parity: the operators you already wrote port mechanically.",[921,2417,2419],{"id":2418},"residual-manual-fixes","Residual manual fixes",[320,2421,2422],{},"The import swap gets a typical port compiling except for a short, enumerated set of deliberate divergences. Walk this checklist once — each is a one-line fix that surfaces at compile time (or, for the last two, at startup):",[344,2424,2425,2446,2468,2498,2520,2535,2564,2586,2601],{},[347,2426,2427,2435,2436,380,2439,462,2442,2445],{},[324,2428,2429,462,2432],{},[354,2430,2431],{},"JoinWindows.beforeMs",[354,2433,2434],{},"afterMs"," are getters here, not public fields: replace Java field access ",[354,2437,2438],{},"jw.beforeMs",[354,2440,2441],{},"jw.getBeforeMs()",[354,2443,2444],{},"jw.getAfterMs()",". Kotlin property access is unaffected.",[347,2447,2448,2453,2454,2457,2458,2461,2462,462,2465,368],{},[324,2449,2450],{},[354,2451,2452],{},"VersionedRecord.validTo()"," returns ",[354,2455,2456],{},"Optional\u003CLong>"," instead of ",[354,2459,2460],{},"long"," — use ",[354,2463,2464],{},".orElse(...)",[354,2466,2467],{},".isPresent()",[347,2469,2470,2475,2476,462,2479,2482,2483,2486,2487,2489,2490,2493,2494,2497],{},[324,2471,2472,2473],{},"Exception handlers receive a ",[354,2474,1054],{},", not separate key\u002Fvalue: read ",[354,2477,2478],{},"record.key()",[354,2480,2481],{},"record.value()"," in ",[354,2484,2485],{},"ProcessingExceptionHandler.handle(...)",". Production ",[422,2488,1983],{}," failures also arrive through ",[354,2491,2492],{},"handle(...)"," — branch on ",[354,2495,2496],{},"(context as ProductionContext).failedOn"," instead of overriding a separate method.",[347,2499,2500,2506,2507,2510,2511,2514,2515,368],{},[324,2501,2502,2505],{},[354,2503,2504],{},"StreamPartitioner"," import is mis-routed by a bulk rewrite",": it lives in ",[354,2508,2509],{},"io.stoatflow.core.topology.StreamPartitioner",", not under ",[354,2512,2513],{},"processor.*"," as in Kafka Streams — fix that one import by hand. Multicast partitioners (KIP-837) are honoured on sinks but ",[324,2516,2517,2518],{},"rejected on ",[354,2519,2414],{},[347,2521,2522,2527,2528,2457,2531,2534],{},[324,2523,2524],{},[354,2525,2526],{},"ValueTransformerWithKey.init"," takes a ",[354,2529,2530],{},"FixedKeyProcessorContext",[354,2532,2533],{},"ProcessorContext"," — change the parameter type; state-store access is identical.",[347,2536,2537,2544,2545,2548,2549,2552,2553,2556,2557,2560,2561,368],{},[324,2538,2539,2540,2543],{},"Custom store suppliers must come from ",[354,2541,2542],{},"Stores.*"," factories"," — hand-rolled ",[354,2546,2547],{},"*BytesStoreSupplier"," implementations are rejected. ",[354,2550,2551],{},"Stores.*WithHeadersBuilder(...)"," needs a ",[354,2554,2555],{},"*WithHeaders(...)"," supplier, and an explicit ",[354,2558,2559],{},"StreamJoined"," store supplier must set ",[354,2562,2563],{},"retainDuplicates = true",[347,2565,2566,2578,2579,2582,2583,368],{},[324,2567,2568,352,2571,462,2574,2577],{},[354,2569,2570],{},"Suppressed",[354,2572,2573],{},"maxBytes",[354,2575,2576],{},"withMaxBytes"," throw"," (suppress buffers hold objects in memory, not bytes) — use ",[354,2580,2581],{},"maxRecords(...)"," or ",[354,2584,2585],{},"unbounded()",[347,2587,2588,2594,2595,462,2598,368],{},[324,2589,2590,2593],{},[354,2591,2592],{},"KStream.transform*"," is absent"," (removed in Kafka Streams 4.x too) — use ",[354,2596,2597],{},"process",[354,2599,2600],{},"processValues",[347,2602,2603,2610],{},[324,2604,2605,2606,2609],{},"A configured ",[354,2607,2608],{},"processor.wrapper.class"," throws at startup"," rather than being silently ignored — remove the key, or apply the wrapper logic directly in your processors.",[320,2612,2613,2614,2617,2618,2621,2622,2625,2626,2629,2630,2633,2634,368],{},"Two behavioural shifts need no code change but are worth knowing: ",[354,2615,2616],{},"taskId()"," is always ",[354,2619,2620],{},"0_0"," (single instance — don't branch on it), and ",[354,2623,2624],{},"ProcessorContext.currentStreamTimeMs()"," returns the ",[324,2627,2628],{},"watermark"," — global, and generally lower than KS per-task stream time. The full catalogue, including everything that needs ",[422,2631,2632],{},"no"," change, is the ",[336,2635,1138],{"href":302},[430,2637,2639],{"id":2638},"step-3-what-to-remove-what-to-keep","Step 3 — what to remove, what to keep",[320,2641,2642,2643,2645,2646,2649,2650,2653,2654,2657],{},"The bulk of a Kafka Streams ",[354,2644,533],{}," block carries over as ",[354,2647,2648],{},"stoatflow.kafka.*"," passthrough or has a direct ",[354,2651,2652],{},"stoatflow.*"," equivalent. A handful of properties model a multi-instance cluster and have ",[324,2655,2656],{},"no meaning"," in StoatFlow's single-instance model — drop them.",[414,2659,2660],{"color":416,"icon":417},[320,2661,2662,2668,2669,2671,2672,462,2675,2678,2679,462,2682,2685,2686,2689],{},[324,2663,2664,2665,2667],{},"Keeping your KS ",[354,2666,533],{},"?"," If you build against ",[354,2670,2346],{}," directly, the KS-keyed config carries over wholesale: ",[354,2673,2674],{},"new StreamsConfig(props)",[354,2676,2677],{},"StreamsConfig.fromProperties(props).build()"," accepts the full Kafka Streams key surface. Recognised-but-inapplicable cluster keys (the table below) log a WARN and are ignored, client keys pass through under their ",[354,2680,2681],{},"consumer.",[354,2683,2684],{},"producer."," prefixes, and truly-unknown keys warn (or fail fast with ",[354,2687,2688],{},"stoatflow.config.strict=true","). The YAML shape on this page is the recommended end state — the adapter just lets you defer the config rewrite.",[921,2691,2693],{"id":2692},"remove-these-model-a-cluster-that-doesnt-exist","Remove — these model a cluster that doesn't exist",[435,2695,2696,2706],{},[438,2697,2698],{},[441,2699,2700,2703],{},[444,2701,2702],{},"Kafka Streams property",[444,2704,2705],{},"Why it goes",[451,2707,2708,2723,2739,2755,2766,2776],{},[441,2709,2710,2714],{},[456,2711,2712],{},[354,2713,405],{},[456,2715,2716,2717,2720,2721,368],{},"There is no thread-per-task pool. Parallelism is set by ",[354,2718,2719],{},"stoatflow.lanes.count",", which scales with cores rather than partition count. See ",[336,2722,57],{"href":58},[441,2724,2725,2730],{},[456,2726,2727],{},[354,2728,2729],{},"num.standby.replicas",[456,2731,2732,2733,2736,2737,368],{},"This KS knob speeds task hand-off during rebalancing; StoatFlow has no rebalancing, so it has no equivalent. HA is fast restart by default, or StoatFlow's own opt-in hot standby — configured under ",[354,2734,2735],{},"stoatflow.ha.*",", not via this knob. See ",[336,2738,233],{"href":234},[441,2740,2741,2752],{},[456,2742,2743,530,2746,530,2749],{},[354,2744,2745],{},"acceptable.recovery.lag",[354,2747,2748],{},"max.warmup.replicas",[354,2750,2751],{},"probing.rebalance.interval.ms",[456,2753,2754],{},"All rebalancing \u002F task-assignment tuning — no group to rebalance.",[441,2756,2757,2763],{},[456,2758,2759,2762],{},[354,2760,2761],{},"group.instance.id"," (if you set it manually)",[456,2764,2765],{},"StoatFlow forces static membership using the application id. Setting it has no effect.",[441,2767,2768,2773],{},[456,2769,2770],{},[354,2771,2772],{},"application.server",[456,2774,2775],{},"Interactive-query host discovery is a multi-instance concern; state is global in one process.",[441,2777,2778,2784],{},[456,2779,2780,2783],{},[354,2781,2782],{},"replication.factor"," (KS internal-topic key)",[456,2785,2786,2787,2790],{},"StoatFlow uses ",[354,2788,2789],{},"stoatflow.changelog.replication-factor"," for its changelog topics.",[414,2792,2793],{"color":416,"icon":417},[320,2794,2795,2796,2799,2800,1029,2803,2806,2807,2810,2811,2814,2815,2818,2819,368],{},"You don't need to set ",[354,2797,2798],{},"processing.guarantee",". StoatFlow defaults to ",[354,2801,2802],{},"EXACTLY_ONCE",[354,2804,2805],{},"stoatflow.processing-guarantee","); if your Kafka Streams app ran ",[354,2808,2809],{},"exactly_once_v2",", you already match. Mind the default flip, though: Kafka Streams defaults to ",[354,2812,2813],{},"at_least_once",", so a KS app that never set the key was running at-least-once — on StoatFlow it now runs under exactly-once (stronger, but transactional output and a different commit-latency profile) unless you set ",[354,2816,2817],{},"AT_LEAST_ONCE"," explicitly. See ",[336,2820,2821],{"href":53},"Exactly-once",[921,2823,2825],{"id":2824},"keep-these-carry-over","Keep — these carry over",[435,2827,2828,2838],{},[438,2829,2830],{},[441,2831,2832,2835],{},[444,2833,2834],{},"Kafka Streams concern",[444,2836,2837],{},"Where it goes in StoatFlow",[451,2839,2840,2848,2856,2868,2886,2898,2914,2929],{},[441,2841,2842,2845],{},[456,2843,2844],{},"Topology code (DSL operators, processors)",[456,2846,2847],{},"Unchanged, modulo the import namespace (Step 2).",[441,2849,2850,2853],{},[456,2851,2852],{},"Topic names",[456,2854,2855],{},"Unchanged — they're string literals in the topology.",[441,2857,2858,2865],{},[456,2859,2860,2861,2864],{},"Serdes (",[354,2862,2863],{},"Serdes.*",", Avro\u002FProtobuf\u002FJSON)",[456,2866,2867],{},"Unchanged — Kafka serde classes are reused as-is.",[441,2869,2870,2875],{},[456,2871,2872],{},[354,2873,2874],{},"application.id",[456,2876,2877,2878,2881,2882,2885],{},"Becomes ",[354,2879,2880],{},"stoatflow.application-id"," — but ",[324,2883,2884],{},"pick a new value"," (see below).",[441,2887,2888,2893],{},[456,2889,2890],{},[354,2891,2892],{},"bootstrap.servers",[456,2894,2877,2895,368],{},[354,2896,2897],{},"stoatflow.bootstrap-servers",[441,2899,2900,2908],{},[456,2901,2902,462,2905],{},[354,2903,2904],{},"default.key.serde",[354,2906,2907],{},"default.value.serde",[456,2909,2910,2913],{},[354,2911,2912],{},"streamsConfigOverrides { defaultKeySerde(...) }"," in code, or the YAML class-name keys.",[441,2915,2916,2921],{},[456,2917,2918],{},[354,2919,2920],{},"schema.registry.url",[456,2922,2923,2926,2927,368],{},[354,2924,2925],{},"stoatflow.schema-registry-url"," — propagated to default serdes automatically. See ",[336,2928,1988],{"href":146},[441,2930,2931,2952],{},[456,2932,2933,2934,530,2937,530,2940,530,2943,530,2946,530,2949,2951],{},"Raw consumer\u002Fproducer tuning (",[354,2935,2936],{},"max.poll.records",[354,2938,2939],{},"fetch.min.bytes",[354,2941,2942],{},"compression.type",[354,2944,2945],{},"linger.ms",[354,2947,2948],{},"acks",[354,2950,392],{},", security\u002FSSL\u002FSASL, …)",[456,2953,2954,2955,462,2958,2961,2962,2965,2966,368],{},"Passed straight through under ",[354,2956,2957],{},"stoatflow.kafka.consumer",[354,2959,2960],{},"stoatflow.kafka.producer"," using the ",[324,2963,2964],{},"exact"," Kafka property names. See ",[336,2967,2968],{"href":173},"Kafka client config",[414,2970,2972],{"color":864,"icon":2971},"i-lucide-shield",[320,2973,2974,2975,2978,2979,850,2982,2984,2985,2988,2989,2991,2992,368],{},"A few Kafka client properties are ",[324,2976,2977],{},"forced"," by StoatFlow and can't be overridden through passthrough: ",[354,2980,2981],{},"group.id",[354,2983,2761],{}," (both set to the application id, for static single-member membership) and ",[354,2986,2987],{},"enable.auto.commit=false"," (offsets are committed by StoatFlow's commit protocol). Setting them under ",[354,2990,2957],{}," has no effect — full list in ",[336,2993,2968],{"href":2994},"\u002Fdocs\u002Fconfiguration\u002Fkafka-client-config#forced-overrides",[921,2996,2998],{"id":2997},"use-a-new-application-id","Use a new application id",[320,3000,3001,3002,368],{},"This is the one config value you must change rather than copy. The application id is StoatFlow's consumer group id (and transactional id). Reusing the Kafka Streams group id would have the StoatFlow app inherit the old committed offsets — it would resume mid-stream and ",[324,3003,3004],{},"skip the historical records that stateful operators need to rebuild from",[320,3006,3007,3008,393,3010,3012],{},"A fresh id gives the StoatFlow app an independent consumer group with no committed offsets, so it can read the source from the beginning. StoatFlow already defaults ",[354,3009,392],{},[354,3011,396],{}," (matching Kafka Streams), so a stateful topology rebuilds from the start with no extra config — set it explicitly only to make the intent obvious or to choose otherwise:",[583,3014,3016],{"className":2188,"code":3015,"language":2190,"meta":589,"style":589},"stoatflow:\n  application-id: map-filter-stoatflow        # NOT the old KS application.id\n  kafka:\n    consumer:\n      auto.offset.reset: earliest             # the default; shown here to make the intent explicit\n",[354,3017,3018,3024,3035,3042,3049],{"__ignoreMap":589},[593,3019,3020,3022],{"class":595,"line":596},[593,3021,779],{"class":2202},[593,3023,2205],{"class":1224},[593,3025,3026,3028,3030,3032],{"class":595,"line":603},[593,3027,2210],{"class":2202},[593,3029,2185],{"class":1224},[593,3031,2215],{"class":623},[593,3033,3034],{"class":599},"        # NOT the old KS application.id\n",[593,3036,3037,3040],{"class":595,"line":614},[593,3038,3039],{"class":2202},"  kafka",[593,3041,2205],{"class":1224},[593,3043,3044,3047],{"class":595,"line":630},[593,3045,3046],{"class":2202},"    consumer",[593,3048,2205],{"class":1224},[593,3050,3051,3054,3056,3059],{"class":595,"line":636},[593,3052,3053],{"class":2202},"      auto.offset.reset",[593,3055,2185],{"class":1224},[593,3057,3058],{"class":623}," earliest",[593,3060,3061],{"class":599},"             # the default; shown here to make the intent explicit\n",[414,3063,3064],{"color":416,"icon":417},[320,3065,3066,3067,3070,3071,3073,3074,3076,3077,3080,3081,3084,3085,3087],{},"For a ",[324,3068,3069],{},"stateless"," topology, ",[354,3072,392],{}," is your reprocessing choice, not a correctness requirement: ",[354,3075,396],{}," (StoatFlow's default) reprocesses the full history into the new output; setting ",[354,3078,3079],{},"latest"," (the raw Kafka client default) starts the StoatFlow app from the current tip, processing only new records. Pick whichever matches your cutover. For a ",[324,3082,3083],{},"stateful"," topology you almost always want ",[354,3086,396],{},", so the aggregates are complete before you switch traffic over.",[430,3089,3091],{"id":3090},"step-4-run-catch-up-and-switch-traffic","Step 4 — run, catch up, and switch traffic",[320,3093,3094],{},"With the build and config swapped, run the StoatFlow app the same way as any StoatFlow app:",[583,3096,3100],{"className":3097,"code":3098,"language":3099,"meta":589,"style":589},"language-bash shiki shiki-themes vitesse-light","export KAFKA_BOOTSTRAP_SERVERS=localhost:9092\nexport STOATFLOW_LICENSE_KEY=\"key\u002F...from your onboarding email...\"\n\n.\u002Fgradlew run\n","bash",[354,3101,3102,3120,3137,3141],{"__ignoreMap":589},[593,3103,3104,3107,3110,3112,3115,3117],{"class":595,"line":596},[593,3105,3106],{"class":1432},"export",[593,3108,3109],{"class":1436}," KAFKA_BOOTSTRAP_SERVERS",[593,3111,1225],{"class":1224},[593,3113,3114],{"class":1436},"localhost",[593,3116,2185],{"class":610},[593,3118,3119],{"class":1436},"9092\n",[593,3121,3122,3124,3127,3129,3131,3134],{"class":595,"line":603},[593,3123,3106],{"class":1432},[593,3125,3126],{"class":1436}," STOATFLOW_LICENSE_KEY",[593,3128,1225],{"class":1224},[593,3130,1481],{"class":1474},[593,3132,3133],{"class":623},"key\u002F...from your onboarding email...",[593,3135,3136],{"class":1474},"\"\n",[593,3138,3139],{"class":595,"line":614},[593,3140,646],{"emptyLinePlaceholder":645},[593,3142,3143,3146],{"class":595,"line":630},[593,3144,3145],{"class":606},".\u002Fgradlew",[593,3147,3148],{"class":623}," run\n",[320,3150,3151,3152,3154],{},"Because the StoatFlow app uses a ",[324,3153,330],{}," application id and writes to its own state and (if stateful) its own changelog topics, it runs independently of the still-running Kafka Streams app — no coordination, no shared group. The recommended cutover:",[506,3156,3157,3167,3177,3201,3206],{},[347,3158,3159,3162,3163,3166],{},[324,3160,3161],{},"Start the StoatFlow app"," pointed at the same source topics, with the new id and ",[354,3164,3165],{},"auto.offset.reset: earliest",". It begins consuming from the start of the source and rebuilding state.",[347,3168,3169,3172,3173,3176],{},[324,3170,3171],{},"Write to a parallel output"," while validating. Either point ",[354,3174,3175],{},"to(...)"," at new output topics, or run a side-by-side comparison against the Kafka Streams output. This is the safe default — you confirm correctness before anything downstream depends on it.",[347,3178,3179,3182,3183,352,3186,3188,3189,3192,3193,3195,3196,3198,3199,368],{},[324,3180,3181],{},"Watch it catch up."," Consumer lag falls toward zero as the StoatFlow app reaches the live tip. Readiness comes up once state restoration completes. ",[422,3184,3185],{},"What you see:",[354,3187,2331],{}," returns 200, the ",[354,3190,3191],{},"stoatflow.consumer.lag.records"," metric trends to ~0, and ",[354,3194,2338],{}," shows stores fully restored. See ",[336,3197,195],{"href":196}," and the ",[336,3200,2342],{"href":186},[347,3202,3203,3205],{},[324,3204,546],{}," to the StoatFlow output once lag is near zero and the outputs match.",[347,3207,3208,3211],{},[324,3209,3210],{},"Retire the Kafka Streams app."," Stop it; once you're confident, delete its consumer group and its internal repartition\u002Fchangelog topics.",[320,3213,3214],{},"The same cutover as a timeline — the two apps overlap until the switch:",[583,3216,3220],{"className":3217,"code":3218,"language":3219,"meta":589,"style":589},"language-mermaid shiki shiki-themes vitesse-light","flowchart LR\n    KS([\"Kafka Streams app —\u003Cbr\u002F>untouched, still serving\"]) -.until the switch.-> S4\n    S1[\"1. Start StoatFlow\u003Cbr\u002F>fresh id, earliest\"] --> S2[\"2. Write to a\u003Cbr\u002F>parallel output\"]\n    S2 --> S3[\"3. Catch up —\u003Cbr\u002F>lag → ~0, ready\"]\n    S3 --> S4[\"4. Switch\u003Cbr\u002F>downstream traffic\"]\n    S4 --> S5[\"5. Retire the\u003Cbr\u002F>KS app\"]\n","mermaid",[354,3221,3222,3227,3232,3237,3242,3247],{"__ignoreMap":589},[593,3223,3224],{"class":595,"line":596},[593,3225,3226],{"class":610},"flowchart LR\n",[593,3228,3229],{"class":595,"line":603},[593,3230,3231],{"class":610},"    KS([\"Kafka Streams app —\u003Cbr\u002F>untouched, still serving\"]) -.until the switch.-> S4\n",[593,3233,3234],{"class":595,"line":614},[593,3235,3236],{"class":610},"    S1[\"1. Start StoatFlow\u003Cbr\u002F>fresh id, earliest\"] --> S2[\"2. Write to a\u003Cbr\u002F>parallel output\"]\n",[593,3238,3239],{"class":595,"line":630},[593,3240,3241],{"class":610},"    S2 --> S3[\"3. Catch up —\u003Cbr\u002F>lag → ~0, ready\"]\n",[593,3243,3244],{"class":595,"line":636},[593,3245,3246],{"class":610},"    S3 --> S4[\"4. Switch\u003Cbr\u002F>downstream traffic\"]\n",[593,3248,3249],{"class":595,"line":642},[593,3250,3251],{"class":610},"    S4 --> S5[\"5. Retire the\u003Cbr\u002F>KS app\"]\n",[414,3253,3254],{"color":864,"icon":80},[320,3255,3256,3259],{},[324,3257,3258],{},"Don't point two writers at the same output topic during the overlap."," While both apps run, have the StoatFlow app write to a parallel output (or run it in compare-only mode). Switching the output topic is the cutover — do it once, deliberately, after validation. There is no protocol to coordinate a Kafka Streams app and a StoatFlow app writing the same output.",[414,3261,3262],{"color":416,"icon":417},[320,3263,3264,3265,462,3267,3270,3271,3274,3275,368],{},"StoatFlow re-keying (",[354,3266,2372],{},[354,3268,3269],{},"groupBy"," \u002F key-changing joins) happens ",[324,3272,3273],{},"in memory"," between lanes — there are no internal repartition topics to provision. The repartition topics your Kafka Streams app created are not used by StoatFlow and can be cleaned up when you retire the old app. See ",[336,3276,3277],{"href":83},"How StoatFlow differs from KS → in-memory re-keying",[430,3279,3281],{"id":3280},"verify-before-you-cut-over","Verify before you cut over",[320,3283,3284,3285,2185],{},"A topology test driver runs the StoatFlow topology in-memory with no broker — use it to confirm the ported topology behaves identically to the Kafka Streams original before any live cutover. The harness mirrors the Kafka Streams ",[354,3286,3287],{},"TopologyTestDriver",[1199,3289,3290,3458],{"group":1201},[583,3291,3293],{"className":585,"code":3292,"filename":1205,"language":588,"meta":589,"style":589},"val driver = TopologyTestDriver.fromBuilder(builder)\nval input = driver.createInputTopic\u003CString, String>(\"input-topic\", Serdes.String(), Serdes.String())\nval output = driver.createOutputTopic\u003CString, String>(\"output-topic\", Serdes.String(), Serdes.String())\n\ninput.pipeInput(\"k\", \"streaming\")\nassertThat(output.readRecord()?.value).isEqualTo(\"STREAMING\")\n\ndriver.close()\n",[354,3294,3295,3312,3354,3391,3395,3415,3445,3449],{"__ignoreMap":589},[593,3296,3297,3299,3302,3304,3307,3310],{"class":595,"line":596},[593,3298,1218],{"class":1217},[593,3300,3301],{"class":610}," driver ",[593,3303,1225],{"class":1224},[593,3305,3306],{"class":610}," TopologyTestDriver.",[593,3308,3309],{"class":606},"fromBuilder",[593,3311,1337],{"class":610},[593,3313,3314,3316,3319,3321,3324,3327,3330,3332,3334,3336,3339,3342,3345,3347,3350,3352],{"class":595,"line":603},[593,3315,1218],{"class":1217},[593,3317,3318],{"class":610}," input ",[593,3320,1225],{"class":1224},[593,3322,3323],{"class":610}," driver.",[593,3325,3326],{"class":606},"createInputTopic",[593,3328,3329],{"class":610},"\u003C",[593,3331,1282],{"class":2005},[593,3333,530],{"class":610},[593,3335,1282],{"class":2005},[593,3337,3338],{"class":610},">(",[593,3340,3341],{"class":623},"\"input-topic\"",[593,3343,3344],{"class":610},", Serdes.",[593,3346,1282],{"class":606},[593,3348,3349],{"class":610},"(), Serdes.",[593,3351,1282],{"class":606},[593,3353,1847],{"class":610},[593,3355,3356,3358,3361,3363,3365,3368,3370,3372,3374,3376,3378,3381,3383,3385,3387,3389],{"class":595,"line":614},[593,3357,1218],{"class":1217},[593,3359,3360],{"class":610}," output ",[593,3362,1225],{"class":1224},[593,3364,3323],{"class":610},[593,3366,3367],{"class":606},"createOutputTopic",[593,3369,3329],{"class":610},[593,3371,1282],{"class":2005},[593,3373,530],{"class":610},[593,3375,1282],{"class":2005},[593,3377,3338],{"class":610},[593,3379,3380],{"class":623},"\"output-topic\"",[593,3382,3344],{"class":610},[593,3384,1282],{"class":606},[593,3386,3349],{"class":610},[593,3388,1282],{"class":606},[593,3390,1847],{"class":610},[593,3392,3393],{"class":595,"line":630},[593,3394,646],{"emptyLinePlaceholder":645},[593,3396,3397,3400,3403,3405,3408,3410,3413],{"class":595,"line":636},[593,3398,3399],{"class":610},"input.",[593,3401,3402],{"class":606},"pipeInput",[593,3404,620],{"class":610},[593,3406,3407],{"class":623},"\"k\"",[593,3409,530],{"class":610},[593,3411,3412],{"class":623},"\"streaming\"",[593,3414,627],{"class":610},[593,3416,3417,3420,3423,3426,3429,3432,3435,3438,3440,3443],{"class":595,"line":642},[593,3418,3419],{"class":606},"assertThat",[593,3421,3422],{"class":610},"(output.",[593,3424,3425],{"class":606},"readRecord",[593,3427,3428],{"class":610},"()?.",[593,3430,3431],{"class":1432},"value",[593,3433,3434],{"class":610},").",[593,3436,3437],{"class":606},"isEqualTo",[593,3439,620],{"class":610},[593,3441,3442],{"class":623},"\"STREAMING\"",[593,3444,627],{"class":610},[593,3446,3447],{"class":595,"line":649},[593,3448,646],{"emptyLinePlaceholder":645},[593,3450,3451,3454,3456],{"class":595,"line":657},[593,3452,3453],{"class":610},"driver.",[593,3455,1386],{"class":606},[593,3457,1329],{"class":610},[583,3459,3461],{"className":1418,"code":3460,"filename":1420,"language":1421,"meta":589,"style":589},"var driver = TopologyTestDriver.fromBuilder(builder);\nvar input = driver.createInputTopic(\"input-topic\", Serdes.String(), Serdes.String());\nvar output = driver.createOutputTopic(\"output-topic\", Serdes.String(), Serdes.String());\n\ninput.pipeInput(\"k\", \"streaming\");\nassertThat(output.readRecord().value()).isEqualTo(\"STREAMING\");\n\ndriver.close();\n",[354,3462,3463,3485,3527,3569,3573,3602,3635,3639],{"__ignoreMap":589},[593,3464,3465,3467,3470,3472,3475,3477,3479,3481,3483],{"class":595,"line":596},[593,3466,1433],{"class":1432},[593,3468,3469],{"class":1436}," driver",[593,3471,1440],{"class":1224},[593,3473,3474],{"class":1436}," TopologyTestDriver",[593,3476,368],{"class":1224},[593,3478,3309],{"class":606},[593,3480,620],{"class":1224},[593,3482,1632],{"class":610},[593,3484,1484],{"class":1224},[593,3486,3487,3489,3492,3494,3496,3498,3500,3502,3504,3507,3509,3511,3513,3515,3517,3519,3521,3523,3525],{"class":595,"line":603},[593,3488,1433],{"class":1432},[593,3490,3491],{"class":1436}," input",[593,3493,1440],{"class":1224},[593,3495,3469],{"class":1436},[593,3497,368],{"class":1224},[593,3499,3326],{"class":606},[593,3501,620],{"class":1224},[593,3503,1481],{"class":1474},[593,3505,3506],{"class":623},"input-topic",[593,3508,1481],{"class":1474},[593,3510,1471],{"class":1224},[593,3512,1561],{"class":1436},[593,3514,368],{"class":1224},[593,3516,1282],{"class":606},[593,3518,1662],{"class":1224},[593,3520,1561],{"class":1436},[593,3522,368],{"class":1224},[593,3524,1282],{"class":606},[593,3526,1573],{"class":1224},[593,3528,3529,3531,3534,3536,3538,3540,3542,3544,3546,3549,3551,3553,3555,3557,3559,3561,3563,3565,3567],{"class":595,"line":614},[593,3530,1433],{"class":1432},[593,3532,3533],{"class":1436}," output",[593,3535,1440],{"class":1224},[593,3537,3469],{"class":1436},[593,3539,368],{"class":1224},[593,3541,3367],{"class":606},[593,3543,620],{"class":1224},[593,3545,1481],{"class":1474},[593,3547,3548],{"class":623},"output-topic",[593,3550,1481],{"class":1474},[593,3552,1471],{"class":1224},[593,3554,1561],{"class":1436},[593,3556,368],{"class":1224},[593,3558,1282],{"class":606},[593,3560,1662],{"class":1224},[593,3562,1561],{"class":1436},[593,3564,368],{"class":1224},[593,3566,1282],{"class":606},[593,3568,1573],{"class":1224},[593,3570,3571],{"class":595,"line":630},[593,3572,646],{"emptyLinePlaceholder":645},[593,3574,3575,3578,3580,3582,3584,3586,3589,3591,3593,3595,3598,3600],{"class":595,"line":636},[593,3576,3577],{"class":1436},"input",[593,3579,368],{"class":1224},[593,3581,3402],{"class":606},[593,3583,620],{"class":1224},[593,3585,1481],{"class":1474},[593,3587,3588],{"class":623},"k",[593,3590,1481],{"class":1474},[593,3592,1471],{"class":1224},[593,3594,1475],{"class":1474},[593,3596,3597],{"class":623},"streaming",[593,3599,1481],{"class":1474},[593,3601,1484],{"class":1224},[593,3603,3604,3606,3608,3611,3613,3615,3617,3619,3622,3624,3626,3628,3631,3633],{"class":595,"line":642},[593,3605,3419],{"class":606},[593,3607,620],{"class":1224},[593,3609,3610],{"class":1436},"output",[593,3612,368],{"class":1224},[593,3614,3425],{"class":606},[593,3616,1231],{"class":1224},[593,3618,3431],{"class":606},[593,3620,3621],{"class":1224},"()).",[593,3623,3437],{"class":606},[593,3625,620],{"class":1224},[593,3627,1481],{"class":1474},[593,3629,3630],{"class":623},"STREAMING",[593,3632,1481],{"class":1474},[593,3634,1484],{"class":1224},[593,3636,3637],{"class":595,"line":649},[593,3638,646],{"emptyLinePlaceholder":645},[593,3640,3641,3644,3646,3648],{"class":595,"line":657},[593,3642,3643],{"class":1436},"driver",[593,3645,368],{"class":1224},[593,3647,1386],{"class":606},[593,3649,1448],{"class":1224},[320,3651,3652,3653,3656,3657,3659,3660,368],{},"Add the test dependency (",[354,3654,3655],{},"io.stoatflow:stoatflow-test-utils",", test scope — see ",[336,3658,18],{"href":19},"). Full coverage of the driver, time control, and state-store assertions is in ",[336,3661,3662],{"href":106},"Testing",[430,3664,3666],{"id":3665},"next-steps","Next steps",[344,3668,3669,3676,3683,3693,3700,3707],{},[347,3670,3671,3675],{},[324,3672,3673],{},[336,3674,338],{"href":270}," — when you can't re-read the source and must reconstruct state another way.",[347,3677,3678,3682],{},[324,3679,3680],{},[336,3681,82],{"href":83}," — the conceptual deltas behind every step here.",[347,3684,3685,3689,3690,3692],{},[324,3686,3687],{},[336,3688,281],{"href":282}," — every ",[354,3691,2652],{}," key with type and default.",[347,3694,3695,3699],{},[324,3696,3697],{},[336,3698,1138],{"href":302}," — method-by-method DSL parity.",[347,3701,3702,3706],{},[324,3703,3704],{},[336,3705,253],{"href":254}," — before the cutover goes live.",[347,3708,3709,3710,3714],{},"Migrating something non-trivial? ",[336,3711,3713],{"href":3712},"\u002Fcontact","Get in touch"," — real people read every email during the alpha.",[3716,3717,3718],"style",{},"html pre.shiki code .s8zF2, html code.shiki .s8zF2{--shiki-default:#A0ADA0}html pre.shiki code .sySUi, html code.shiki .sySUi{--shiki-default:#59873A}html pre.shiki code .suHK_, html code.shiki .suHK_{--shiki-default:#393A34}html pre.shiki code .spphp, html code.shiki .spphp{--shiki-default:#B56959}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 .sbBg2, html code.shiki .sbBg2{--shiki-default:#1E754F}html pre.shiki code .sYZai, html code.shiki .sYZai{--shiki-default:#999999}html pre.shiki code .si04Y, html code.shiki .si04Y{--shiki-default:#AB5959}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 .sUxyF, html code.shiki .sUxyF{--shiki-default:#2E8F82}html pre.shiki code .su6XF, html code.shiki .su6XF{--shiki-default:#998418}",{"title":589,"searchDepth":603,"depth":603,"links":3720},[3721,3722,3723,3724,3729,3734,3735,3736],{"id":432,"depth":603,"text":433},{"id":503,"depth":603,"text":504},{"id":557,"depth":603,"text":558},{"id":877,"depth":603,"text":878,"children":3725},[3726,3727,3728],{"id":898,"depth":614,"text":923},{"id":1183,"depth":614,"text":1184},{"id":2418,"depth":614,"text":2419},{"id":2638,"depth":603,"text":2639,"children":3730},[3731,3732,3733],{"id":2692,"depth":614,"text":2693},{"id":2824,"depth":614,"text":2825},{"id":2997,"depth":614,"text":2998},{"id":3090,"depth":603,"text":3091},{"id":3280,"depth":603,"text":3281},{"id":3665,"depth":603,"text":3666},"Green-field cutover from Kafka Streams — point StoatFlow at the same source topics with a fresh consumer group, let stateful operators rebuild from changelog\u002Fsource, then switch traffic. The dependency, build, and config swap, with the before\u002Fafter grounded in the map-filter example.","md",{},{"icon":267},"how-to",{"title":264,"description":3737},"tBbH_UW80Pz-sJMw2DOBZUHJvmXJDHV8kh07wmmA1DE",[3745,3747],{"title":258,"path":259,"stem":260,"description":3746,"icon":85,"children":-1},"What carries over unchanged when you move a Kafka Streams topology to StoatFlow, what changes operationally, and how to decide between a green-field cutover and a state-carrying migration.",{"title":269,"path":270,"stem":271,"description":3748,"icon":272,"children":-1},"What it means to carry state across a migration to StoatFlow — reusing changelog topics, offset handling, how restoration rebuilds local state, and what is and is not supported for in-place reuse.",1783713363649]