[{"data":1,"prerenderedAt":1610},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fconcepts\u002Fconfiguration-model":314,"\u002Fdocs\u002Fconcepts\u002Fconfiguration-model-surround":1605},[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":72,"body":316,"description":1598,"extension":1599,"meta":1600,"navigation":1601,"pageType":1602,"path":73,"seo":1603,"stem":74,"__hash__":1604},"docs\u002F1.docs\u002F2.concepts\u002F7.configuration-model.md",{"type":317,"value":318,"toc":1585},"minimark",[319,323,375,380,383,401,454,477,480,507,510,642,652,656,659,662,725,728,733,745,755,994,997,1011,1053,1062,1066,1069,1079,1123,1141,1144,1151,1165,1175,1441,1453,1470,1474,1480,1487,1497,1501,1508,1522,1529,1533,1546,1550,1581],[320,321,322],"p",{},"This page explains how a StoatFlow application is configured: the sources you can set values from, the order they win in, and the line between what you configure and what the engine decides for itself. It describes the model, not every key — for the exact key list and defaults, the configuration classes in the source are authoritative.",[324,325,326],"tldr-panel",{},[327,328,329,354,360,369],"ul",{},[330,331,332,336,337,341,342,345,346,349,350,353],"li",{},[333,334,335],"strong",{},"One file to start:"," ",[338,339,340],"code",{},"application.yaml"," on the classpath, with three top-level groups — ",[338,343,344],{},"stoatflow:"," (the engine), ",[338,347,348],{},"runtime:"," (the HTTP\u002Fmetrics\u002Fhealth wrapper), and ",[338,351,352],{},"logging:"," (per-logger levels).",[330,355,356,359],{},[333,357,358],{},"Layered:"," YAML defaults can be overlaid by external files, environment variables, and a programmatic override block — each layer wins over the one below it.",[330,361,362,365,366,368],{},[333,363,364],{},"Opinionated defaults:"," almost everything has a sensible default, so a minimal ",[338,367,340],{}," is two lines plus a license. You override only what your deployment needs.",[330,370,371,374],{},[333,372,373],{},"Some behaviour is adaptive:"," the commit cadence, for example, self-tunes at runtime rather than reading a fixed interval. You set bounds and intent; the engine picks the value inside them.",[376,377,379],"h2",{"id":378},"two-configuration-surfaces","Two configuration surfaces",[320,381,382],{},"A StoatFlow application is configured at two layers, and it helps to keep them straight.",[320,384,385,386,389,390,393,394,397,398,400],{},"The ",[333,387,388],{},"engine"," — the ",[338,391,392],{},":core"," library — is configured by a ",[338,395,396],{},"StreamsConfig"," object. If you build directly against ",[338,399,392],{},", you construct that object yourself, in code.",[320,402,403,404,406,407,410,411,414,415,417,418,421,422,425,426,429,430,421,433,421,436,439,440,443,444,447,448,453],{},"Porting a Kafka Streams application onto ",[338,405,392],{}," doesn't mean rewriting that config from scratch. A KS-keyed ",[338,408,409],{},"Properties"," or ",[338,412,413],{},"Map"," adapts onto the same typed ",[338,416,396],{}," — ",[338,419,420],{},"StreamsConfig.fromProperties(props)"," \u002F ",[338,423,424],{},"fromMap(map)",", and ",[338,427,428],{},"new StreamsConfig(props)"," works directly (the KS-faithful constructor shape). The adapter covers the Kafka-Streams-4.x overlap surface — typed keys, class-name pluggables (serdes, exception handlers), and the ",[338,431,432],{},"consumer.",[338,434,435],{},"producer.",[338,437,438],{},"admin."," client prefixes — and treats a key StoatFlow doesn't recognise leniently: a warning, not a failure, unless you opt into ",[338,441,442],{},"stoatflow.config.strict",". ",[338,445,446],{},"StreamsConfig.toProperties()"," emits the resolved config back out for inspection. The ",[449,450,452],"a",{"href":451},"\u002Fdocs\u002Freference\u002Fks-compatibility-matrix#configuration","KS-compatibility matrix"," lists the exact key coverage.",[320,455,385,456,389,459,462,463,465,466,468,469,471,472,476],{},[333,457,458],{},"runtime",[338,460,461],{},":runtime"," module, the batteries-included wrapper most applications use — adds YAML loading on top. It reads an ",[338,464,340],{},", maps it onto the engine's ",[338,467,396],{},", and configures its own infrastructure (the HTTP server, metrics, health checks) from the same file. This page is mostly about that runtime layer, because that's where the layering and precedence live. (Why a runtime wrapper exists at all is covered on ",[449,470,47],{"href":48}," and ",[449,473,475],{"href":474},"\u002Fproduct\u002Ffeatures","Features",".)",[320,478,479],{},"The YAML splits into three top-level groups:",[327,481,482,489,496],{},[330,483,484,488],{},[333,485,486],{},[338,487,344],{}," — the engine. Kafka connection, lane parallelism, commit barriers, state stores, changelog topics, watermarks, the license, and the rest of the processing behaviour.",[330,490,491,495],{},[333,492,493],{},[338,494,348],{}," — the infrastructure wrapper. The HTTP server, Prometheus metrics, endpoint visibility, and health checks.",[330,497,498,502,503,506],{},[333,499,500],{},[338,501,352],{}," — per-logger levels, applied programmatically over whatever ",[338,504,505],{},"logback.xml"," defines.",[320,508,509],{},"A minimal file is short, because the defaults carry the rest:",[511,512,517],"pre",{"className":513,"code":514,"language":515,"meta":516,"style":516},"language-yaml shiki shiki-themes vitesse-light","stoatflow:\n  application-id: word-count\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","",[338,518,519,532,545,556,563,571,582,587,594,602,614,625,633],{"__ignoreMap":516},[520,521,524,528],"span",{"class":522,"line":523},"line",1,[520,525,527],{"class":526},"su6XF","stoatflow",[520,529,531],{"class":530},"sYZai",":\n",[520,533,535,538,541],{"class":522,"line":534},2,[520,536,537],{"class":526},"  application-id",[520,539,540],{"class":530},":",[520,542,544],{"class":543},"spphp"," word-count\n",[520,546,548,551,553],{"class":522,"line":547},3,[520,549,550],{"class":526},"  bootstrap-servers",[520,552,540],{"class":530},[520,554,555],{"class":543}," ${KAFKA_BOOTSTRAP_SERVERS:-localhost:9092}\n",[520,557,559],{"class":522,"line":558},4,[520,560,562],{"emptyLinePlaceholder":561},true,"\n",[520,564,566,569],{"class":522,"line":565},5,[520,567,568],{"class":526},"  license",[520,570,531],{"class":530},[520,572,574,577,579],{"class":522,"line":573},6,[520,575,576],{"class":526},"    key",[520,578,540],{"class":530},[520,580,581],{"class":543}," ${STOATFLOW_LICENSE_KEY}\n",[520,583,585],{"class":522,"line":584},7,[520,586,562],{"emptyLinePlaceholder":561},[520,588,590,592],{"class":522,"line":589},8,[520,591,458],{"class":526},[520,593,531],{"class":530},[520,595,597,600],{"class":522,"line":596},9,[520,598,599],{"class":526},"  http",[520,601,531],{"class":530},[520,603,605,608,610],{"class":522,"line":604},10,[520,606,607],{"class":526},"    enabled",[520,609,540],{"class":530},[520,611,613],{"class":612},"sbBg2"," true\n",[520,615,617,620,622],{"class":522,"line":616},11,[520,618,619],{"class":526},"    port",[520,621,540],{"class":530},[520,623,624],{"class":543}," ${HTTP_PORT:-8080}\n",[520,626,628,631],{"class":522,"line":627},12,[520,629,630],{"class":526},"  metrics",[520,632,531],{"class":530},[520,634,636,638,640],{"class":522,"line":635},13,[520,637,607],{"class":526},[520,639,540],{"class":530},[520,641,613],{"class":612},[320,643,385,644,647,648,651],{},[338,645,646],{},"${VAR:-default}"," syntax is environment-variable interpolation with a fallback — resolve the variable, or use the value after ",[338,649,650],{},":-"," when it's unset. Use it to keep deployment-specific values (broker addresses, ports, secrets) out of the committed file.",[376,653,655],{"id":654},"the-layers-and-how-they-stack","The layers, and how they stack",[320,657,658],{},"Configuration is resolved by overlaying several sources. A value set in a higher-priority layer overrides the same value in every layer below it. Anything no layer sets falls through to the built-in default.",[320,660,661],{},"From highest priority to lowest:",[663,664,665,676,686,695,705,713,719],"ol",{},[330,666,667,670,671,675],{},[333,668,669],{},"Programmatic overrides"," — a code block passed when you construct the runtime. Applied last, so they win over everything from YAML and the environment. This is also the ",[672,673,674],"em",{},"only"," layer that can carry non-serializable values — serdes, exception handlers, callbacks — that can't be expressed as YAML strings.",[330,677,678,685],{},[333,679,680,681,684],{},"Environment variables (",[338,682,683],{},"__"," separator)"," — the recommended form for overriding nested keys from the environment.",[330,687,688,694],{},[333,689,680,690,693],{},[338,691,692],{},"STOATFLOW_"," prefix, legacy)"," — the older single-underscore form.",[330,696,697,700,701,704],{},[333,698,699],{},"External overlay files"," — extra YAML files named via the ",[338,702,703],{},"STOATFLOW_CONFIG_FILES"," environment variable.",[330,706,707,712],{},[333,708,709],{},[338,710,711],{},"application.yml"," on the classpath.",[330,714,715,712],{},[333,716,717],{},[338,718,340],{},[330,720,721,724],{},[333,722,723],{},"Built-in defaults"," — the values baked into the configuration classes.",[320,726,727],{},"The sections below cover each layer that needs explaining.",[729,730,732],"h3",{"id":731},"the-classpath-yaml","The classpath YAML",[320,734,735,737,738,740,741,744],{},[338,736,340],{}," (or ",[338,739,711],{},") on the classpath is the base layer. For most applications it's the only configuration file. It lives at ",[338,742,743],{},"src\u002Fmain\u002Fresources\u002Fapplication.yaml"," in a typical project.",[320,746,747,748,751,752,754],{},"This is where you express the ",[672,749,750],{},"intended"," shape of the deployment — the values that are the same across every environment, with ",[338,753,646],{}," interpolation for the handful that differ. The example below tunes lanes, the commit barrier, state, and the changelog; everything it omits keeps its default.",[511,756,758],{"className":513,"code":757,"language":515,"meta":516,"style":516},"stoatflow:\n  application-id: stock-tick-filter\n  bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:-localhost:9092}\n\n  lanes:\n    count: 12\n    queue-capacity: 1000\n\n  commit-barrier:\n    interval-ms: 200\n    timeout-ms: 20000\n\n  state:\n    dir: ${java.io.tmpdir}\u002Fstoatflow\u002Fstock-tick-filter\n    restoration-enabled: true\n\n  changelog:\n    enabled: true\n    replication-factor: 1\n    num-partitions: 6\n\nruntime:\n  http:\n    enabled: true\n    port: ${HTTP_PORT:-8080}\n  metrics:\n    enabled: true\n    common-tags:\n      env: development\n",[338,759,760,766,775,783,787,794,805,815,819,826,836,846,850,857,868,878,883,891,900,911,922,927,934,941,950,959,966,975,983],{"__ignoreMap":516},[520,761,762,764],{"class":522,"line":523},[520,763,527],{"class":526},[520,765,531],{"class":530},[520,767,768,770,772],{"class":522,"line":534},[520,769,537],{"class":526},[520,771,540],{"class":530},[520,773,774],{"class":543}," stock-tick-filter\n",[520,776,777,779,781],{"class":522,"line":547},[520,778,550],{"class":526},[520,780,540],{"class":530},[520,782,555],{"class":543},[520,784,785],{"class":522,"line":558},[520,786,562],{"emptyLinePlaceholder":561},[520,788,789,792],{"class":522,"line":565},[520,790,791],{"class":526},"  lanes",[520,793,531],{"class":530},[520,795,796,799,801],{"class":522,"line":573},[520,797,798],{"class":526},"    count",[520,800,540],{"class":530},[520,802,804],{"class":803},"s-TwI"," 12\n",[520,806,807,810,812],{"class":522,"line":584},[520,808,809],{"class":526},"    queue-capacity",[520,811,540],{"class":530},[520,813,814],{"class":803}," 1000\n",[520,816,817],{"class":522,"line":589},[520,818,562],{"emptyLinePlaceholder":561},[520,820,821,824],{"class":522,"line":596},[520,822,823],{"class":526},"  commit-barrier",[520,825,531],{"class":530},[520,827,828,831,833],{"class":522,"line":604},[520,829,830],{"class":526},"    interval-ms",[520,832,540],{"class":530},[520,834,835],{"class":803}," 200\n",[520,837,838,841,843],{"class":522,"line":616},[520,839,840],{"class":526},"    timeout-ms",[520,842,540],{"class":530},[520,844,845],{"class":803}," 20000\n",[520,847,848],{"class":522,"line":627},[520,849,562],{"emptyLinePlaceholder":561},[520,851,852,855],{"class":522,"line":635},[520,853,854],{"class":526},"  state",[520,856,531],{"class":530},[520,858,860,863,865],{"class":522,"line":859},14,[520,861,862],{"class":526},"    dir",[520,864,540],{"class":530},[520,866,867],{"class":543}," ${java.io.tmpdir}\u002Fstoatflow\u002Fstock-tick-filter\n",[520,869,871,874,876],{"class":522,"line":870},15,[520,872,873],{"class":526},"    restoration-enabled",[520,875,540],{"class":530},[520,877,613],{"class":612},[520,879,881],{"class":522,"line":880},16,[520,882,562],{"emptyLinePlaceholder":561},[520,884,886,889],{"class":522,"line":885},17,[520,887,888],{"class":526},"  changelog",[520,890,531],{"class":530},[520,892,894,896,898],{"class":522,"line":893},18,[520,895,607],{"class":526},[520,897,540],{"class":530},[520,899,613],{"class":612},[520,901,903,906,908],{"class":522,"line":902},19,[520,904,905],{"class":526},"    replication-factor",[520,907,540],{"class":530},[520,909,910],{"class":803}," 1\n",[520,912,914,917,919],{"class":522,"line":913},20,[520,915,916],{"class":526},"    num-partitions",[520,918,540],{"class":530},[520,920,921],{"class":803}," 6\n",[520,923,925],{"class":522,"line":924},21,[520,926,562],{"emptyLinePlaceholder":561},[520,928,930,932],{"class":522,"line":929},22,[520,931,458],{"class":526},[520,933,531],{"class":530},[520,935,937,939],{"class":522,"line":936},23,[520,938,599],{"class":526},[520,940,531],{"class":530},[520,942,944,946,948],{"class":522,"line":943},24,[520,945,607],{"class":526},[520,947,540],{"class":530},[520,949,613],{"class":612},[520,951,953,955,957],{"class":522,"line":952},25,[520,954,619],{"class":526},[520,956,540],{"class":530},[520,958,624],{"class":543},[520,960,962,964],{"class":522,"line":961},26,[520,963,630],{"class":526},[520,965,531],{"class":530},[520,967,969,971,973],{"class":522,"line":968},27,[520,970,607],{"class":526},[520,972,540],{"class":530},[520,974,613],{"class":612},[520,976,978,981],{"class":522,"line":977},28,[520,979,980],{"class":526},"    common-tags",[520,982,531],{"class":530},[520,984,986,989,991],{"class":522,"line":985},29,[520,987,988],{"class":526},"      env",[520,990,540],{"class":530},[520,992,993],{"class":543}," development\n",[729,995,699],{"id":996},"external-overlay-files",[320,998,999,1000,1002,1003,1006,1007,1010],{},"Set ",[338,1001,703],{}," to a comma-separated list of file paths and the runtime merges each one ",[672,1004,1005],{},"over"," the bundled classpath YAML, at the property level — a deep merge, not a wholesale replace. A file that sets only ",[338,1008,1009],{},"stoatflow.bootstrap-servers"," overrides that one value and leaves everything else from the base file intact.",[511,1012,1016],{"className":1013,"code":1014,"language":1015,"meta":516,"style":516},"language-bash shiki shiki-themes vitesse-light","# Single overlay\nSTOATFLOW_CONFIG_FILES=\u002Fetc\u002Fmyapp\u002Fproduction.yaml\n\n# Multiple overlays — later files win over earlier ones\nSTOATFLOW_CONFIG_FILES=\u002Fetc\u002Fmyapp\u002Fbase.yaml,\u002Fetc\u002Fmyapp\u002Foverrides.yaml\n","bash",[338,1017,1018,1024,1035,1039,1044],{"__ignoreMap":516},[520,1019,1020],{"class":522,"line":523},[520,1021,1023],{"class":1022},"s8zF2","# Single overlay\n",[520,1025,1026,1029,1032],{"class":522,"line":534},[520,1027,703],{"class":1028},"svycV",[520,1030,1031],{"class":530},"=",[520,1033,1034],{"class":543},"\u002Fetc\u002Fmyapp\u002Fproduction.yaml\n",[520,1036,1037],{"class":522,"line":547},[520,1038,562],{"emptyLinePlaceholder":561},[520,1040,1041],{"class":522,"line":558},[520,1042,1043],{"class":1022},"# Multiple overlays — later files win over earlier ones\n",[520,1045,1046,1048,1050],{"class":522,"line":565},[520,1047,703],{"class":1028},[520,1049,1031],{"class":530},[520,1051,1052],{"class":543},"\u002Fetc\u002Fmyapp\u002Fbase.yaml,\u002Fetc\u002Fmyapp\u002Foverrides.yaml\n",[320,1054,1055,1056,1058,1059,1061],{},"This is the natural fit for environment-specific layering: a base ",[338,1057,340],{}," baked into the artifact, and a per-environment overlay mounted at deploy time (a Kubernetes ConfigMap or Secret, for example). The files listed in ",[338,1060,703],{}," are required — if one is named but missing, startup fails rather than silently skipping it.",[729,1063,1065],{"id":1064},"environment-variables","Environment variables",[320,1067,1068],{},"Any property can be set from the environment, which is what makes the YAML overridable without rebuilding the artifact. There are two naming forms.",[320,1070,1071,1072,1074,1075,1078],{},"The recommended form uses ",[338,1073,683],{}," (double underscore) as the path separator between nested keys, and a single ",[338,1076,1077],{},"_"," for word boundaries inside a key name:",[511,1080,1082],{"className":1013,"code":1081,"language":1015,"meta":516,"style":516},"STOATFLOW__LANES__COUNT=32                          # → stoatflow.lanes.count\nSTOATFLOW__COMMIT_BARRIER__INTERVAL_MS=500          # → stoatflow.commit-barrier.interval-ms\nRUNTIME__HTTP__PORT=9090                            # → runtime.http.port\n",[338,1083,1084,1097,1110],{"__ignoreMap":516},[520,1085,1086,1089,1091,1094],{"class":522,"line":523},[520,1087,1088],{"class":1028},"STOATFLOW__LANES__COUNT",[520,1090,1031],{"class":530},[520,1092,1093],{"class":543},"32",[520,1095,1096],{"class":1022},"                          # → stoatflow.lanes.count\n",[520,1098,1099,1102,1104,1107],{"class":522,"line":534},[520,1100,1101],{"class":1028},"STOATFLOW__COMMIT_BARRIER__INTERVAL_MS",[520,1103,1031],{"class":530},[520,1105,1106],{"class":543},"500",[520,1108,1109],{"class":1022},"          # → stoatflow.commit-barrier.interval-ms\n",[520,1111,1112,1115,1117,1120],{"class":522,"line":547},[520,1113,1114],{"class":1028},"RUNTIME__HTTP__PORT",[520,1116,1031],{"class":530},[520,1118,1119],{"class":543},"9090",[520,1121,1122],{"class":1022},"                            # → runtime.http.port\n",[320,1124,1125,1126,1128,1129,1131,1132,1134,1135,1134,1137,1140],{},"The double-underscore form is the one to reach for — it can address multi-word property names that the legacy single-underscore form (a ",[338,1127,692],{}," prefix with ",[338,1130,1077],{}," doubling as both separators) cannot. Both are case-insensitive and only apply to keys under the known top-level groups (",[338,1133,527],{},", ",[338,1136,458],{},[338,1138,1139],{},"logging",").",[729,1142,669],{"id":1143},"programmatic-overrides",[320,1145,1146,1147,1150],{},"The highest-priority layer is a code block you pass when constructing the runtime. It's applied ",[672,1148,1149],{},"after"," YAML and environment variables are resolved, so anything it sets wins. Two reasons to use it:",[327,1152,1153,1159],{},[330,1154,1155,1158],{},[333,1156,1157],{},"Non-serializable values."," Serdes, exception handlers, and callbacks are Java\u002FKotlin objects, not strings — they can't live in YAML. The override block is where they're set.",[330,1160,1161,1164],{},[333,1162,1163],{},"Programmatic control."," Anything you'd rather compute in code than spell out in YAML.",[320,1166,1167,1168,1171,1172,1174],{},"This is the ",[338,1169,1170],{},"streamsConfigOverrides"," block from ",[449,1173,28],{"href":29}," — here setting the default serdes, which have no YAML representation:",[1176,1177,1179,1301],"code-tabs",{"group":1178},"lang",[511,1180,1185],{"className":1181,"code":1182,"filename":1183,"language":1184,"meta":516,"style":516},"language-kotlin shiki shiki-themes vitesse-light","val runtime = StoatFlowRuntime.fromConfig(\n    topologyBuilder = { buildTopology(it) },\n    configure = {\n        streamsConfigOverrides {\n            defaultKeySerde(Serdes.String())\n            defaultValueSerde(Serdes.String())\n        }\n    },\n)\nruntime.start()\nruntime.awaitTermination()\n","Kotlin","kotlin",[338,1186,1187,1208,1224,1234,1241,1255,1266,1271,1276,1281,1292],{"__ignoreMap":516},[520,1188,1189,1192,1196,1198,1201,1205],{"class":522,"line":523},[520,1190,1191],{"class":612},"val",[520,1193,1195],{"class":1194},"suHK_"," runtime ",[520,1197,1031],{"class":530},[520,1199,1200],{"class":1194}," StoatFlowRuntime.",[520,1202,1204],{"class":1203},"sySUi","fromConfig",[520,1206,1207],{"class":1194},"(\n",[520,1209,1210,1213,1215,1218,1221],{"class":522,"line":534},[520,1211,1212],{"class":1194},"    topologyBuilder ",[520,1214,1031],{"class":530},[520,1216,1217],{"class":1194}," { ",[520,1219,1220],{"class":1203},"buildTopology",[520,1222,1223],{"class":1194},"(it) },\n",[520,1225,1226,1229,1231],{"class":522,"line":547},[520,1227,1228],{"class":1194},"    configure ",[520,1230,1031],{"class":530},[520,1232,1233],{"class":1194}," {\n",[520,1235,1236,1239],{"class":522,"line":558},[520,1237,1238],{"class":1203},"        streamsConfigOverrides",[520,1240,1233],{"class":1194},[520,1242,1243,1246,1249,1252],{"class":522,"line":565},[520,1244,1245],{"class":1203},"            defaultKeySerde",[520,1247,1248],{"class":1194},"(Serdes.",[520,1250,1251],{"class":1203},"String",[520,1253,1254],{"class":1194},"())\n",[520,1256,1257,1260,1262,1264],{"class":522,"line":573},[520,1258,1259],{"class":1203},"            defaultValueSerde",[520,1261,1248],{"class":1194},[520,1263,1251],{"class":1203},[520,1265,1254],{"class":1194},[520,1267,1268],{"class":522,"line":584},[520,1269,1270],{"class":1194},"        }\n",[520,1272,1273],{"class":522,"line":589},[520,1274,1275],{"class":1194},"    },\n",[520,1277,1278],{"class":522,"line":596},[520,1279,1280],{"class":1194},")\n",[520,1282,1283,1286,1289],{"class":522,"line":604},[520,1284,1285],{"class":1194},"runtime.",[520,1287,1288],{"class":1203},"start",[520,1290,1291],{"class":1194},"()\n",[520,1293,1294,1296,1299],{"class":522,"line":616},[520,1295,1285],{"class":1194},[520,1297,1298],{"class":1203},"awaitTermination",[520,1300,1291],{"class":1194},[511,1302,1307],{"className":1303,"code":1304,"filename":1305,"language":1306,"meta":516,"style":516},"language-java shiki shiki-themes vitesse-light","var runtime = StoatFlowRuntime.fromConfig(\n    Main::buildTopology,\n    builder -> builder.streamsConfigOverrides(cfg -> {\n        cfg.defaultKeySerde(Serdes.String());\n        cfg.defaultValueSerde(Serdes.String());\n    })\n);\nruntime.start();\nruntime.awaitTermination();\n","Java","java",[338,1308,1309,1331,1344,1369,1391,1410,1415,1420,1431],{"__ignoreMap":516},[520,1310,1311,1315,1318,1321,1324,1327,1329],{"class":522,"line":523},[520,1312,1314],{"class":1313},"si04Y","var",[520,1316,1317],{"class":1028}," runtime",[520,1319,1320],{"class":530}," =",[520,1322,1323],{"class":1028}," StoatFlowRuntime",[520,1325,1326],{"class":530},".",[520,1328,1204],{"class":1203},[520,1330,1207],{"class":530},[520,1332,1333,1336,1339,1341],{"class":522,"line":534},[520,1334,1335],{"class":1194},"    Main",[520,1337,1338],{"class":612},"::",[520,1340,1220],{"class":1194},[520,1342,1343],{"class":530},",\n",[520,1345,1346,1349,1352,1355,1357,1359,1362,1365,1367],{"class":522,"line":547},[520,1347,1348],{"class":1194},"    builder ",[520,1350,1351],{"class":530},"->",[520,1353,1354],{"class":1028}," builder",[520,1356,1326],{"class":530},[520,1358,1170],{"class":1203},[520,1360,1361],{"class":530},"(",[520,1363,1364],{"class":1194},"cfg ",[520,1366,1351],{"class":530},[520,1368,1233],{"class":530},[520,1370,1371,1374,1376,1379,1381,1384,1386,1388],{"class":522,"line":558},[520,1372,1373],{"class":1028},"        cfg",[520,1375,1326],{"class":530},[520,1377,1378],{"class":1203},"defaultKeySerde",[520,1380,1361],{"class":530},[520,1382,1383],{"class":1028},"Serdes",[520,1385,1326],{"class":530},[520,1387,1251],{"class":1203},[520,1389,1390],{"class":530},"());\n",[520,1392,1393,1395,1397,1400,1402,1404,1406,1408],{"class":522,"line":565},[520,1394,1373],{"class":1028},[520,1396,1326],{"class":530},[520,1398,1399],{"class":1203},"defaultValueSerde",[520,1401,1361],{"class":530},[520,1403,1383],{"class":1028},[520,1405,1326],{"class":530},[520,1407,1251],{"class":1203},[520,1409,1390],{"class":530},[520,1411,1412],{"class":522,"line":573},[520,1413,1414],{"class":530},"    })\n",[520,1416,1417],{"class":522,"line":584},[520,1418,1419],{"class":530},");\n",[520,1421,1422,1424,1426,1428],{"class":522,"line":589},[520,1423,458],{"class":1028},[520,1425,1326],{"class":530},[520,1427,1288],{"class":1203},[520,1429,1430],{"class":530},"();\n",[520,1432,1433,1435,1437,1439],{"class":522,"line":596},[520,1434,458],{"class":1028},[520,1436,1326],{"class":530},[520,1438,1298],{"class":1203},[520,1440,1430],{"class":530},[320,1442,1443,1446,1447,1449,1450,1452],{},[338,1444,1445],{},"StoatFlowRuntime.fromConfig(...)"," loads ",[338,1448,340],{},", applies the environment-variable layers, then runs your ",[338,1451,1170],{}," block last — so a value you set here overrides the same key from YAML or the environment.",[1454,1455,1458],"callout",{"color":1456,"icon":1457},"info","i-lucide-info",[320,1459,1460,1461,1463,1464,1466,1467,1469],{},"If you build against ",[338,1462,392],{}," directly rather than ",[338,1465,461],{},", there is no YAML layer — you construct the engine's ",[338,1468,396],{}," in code, which is the same surface the override block writes to. The runtime's whole job here is to load YAML and the environment, then hand control to that same builder.",[376,1471,1473],{"id":1472},"why-the-defaults-are-opinionated","Why the defaults are opinionated",[320,1475,1476,1477,1479],{},"Most configuration keys have a default, and the defaults are chosen to make a typical application run well without tuning. Lane count defaults to the available CPU count; the commit barrier, state store sizes, changelog topic creation, and watermark behaviour all start from values that suit a common workload. The minimal two-line ",[338,1478,340],{}," works because of this — you override only the keys your deployment actually needs to change.",[320,1481,1482,1483,1486],{},"The intent is that configuration is ",[672,1484,1485],{},"subtractive",": you start from a working default and adjust the few knobs your workload demands (broker address, lane count, commit-interval bounds, state directory), rather than assembling a working configuration from scratch.",[320,1488,1489,1490,1493,1494,1496],{},"Some defaults are also ",[333,1491,1492],{},"derived rather than fixed"," — they're computed from the environment at startup. Lane count, for instance, defaults to a function of the available processor count, so the same ",[338,1495,340],{}," adapts to the machine it runs on instead of pinning a number that's wrong on half your hardware. These derived defaults still sit at the bottom of the precedence stack: set the key explicitly and your value wins.",[376,1498,1500],{"id":1499},"what-stays-automatic","What stays automatic",[320,1502,1503,1504,1507],{},"Not everything that affects behaviour is a configuration key. Some of the engine's behaviour is ",[333,1505,1506],{},"adaptive"," — decided at runtime from what the workload is actually doing, rather than read from a fixed value.",[320,1509,1510,1511,1514,1515,1518,1519,1521],{},"The clearest example is the ",[333,1512,1513],{},"commit cadence",". Rather than committing on a fixed timer, the runtime adjusts how often it commits based on observed runtime conditions, so the cadence tracks the workload instead of forcing you to guess a number that's right for both a quiet topic and a burst. What you configure is the ",[672,1516,1517],{},"envelope"," — bounds and intent, like the minimum and maximum interval between barriers — and the engine chooses a value inside it. The ",[449,1520,47],{"href":48}," page frames this distinction: the barrier scheduling cadence and related runtime decisions are implementation concerns, and the algorithms that drive them stay in the source.",[320,1523,1524,1525,1528],{},"The practical consequence for configuring an application: you don't set the commit interval to a single number and hope it fits every load. You set the bounds that must hold — the slowest acceptable cadence, the fastest — and let the engine pick within them. The same philosophy shows up elsewhere in the engine, where a mode like ",[338,1526,1527],{},"AUTO"," lets the runtime detect the right setting from the topology rather than asking you to specify it. Where a knob is exposed, it's there because a deployment can reasonably need to set it; where behaviour is left automatic, it's because the engine has better information at runtime than you have at configuration time.",[376,1530,1532],{"id":1531},"inspecting-the-resolved-configuration","Inspecting the resolved configuration",[320,1534,1535,1536,1539,1540,1543,1544,1326],{},"Because configuration is layered, the value an application ends up running with isn't always obvious from any single file. The runtime exposes the ",[333,1537,1538],{},"merged, effective configuration"," at the ",[338,1541,1542],{},"\u002Fconfig"," HTTP endpoint, with sensitive values (passwords, secrets, tokens) masked. When you need to confirm what actually won across all the layers, that's the source of truth for a running instance — see the operational surface on ",[449,1545,47],{"href":48},[376,1547,1549],{"id":1548},"where-to-go-next","Where to go next",[327,1551,1552,1557,1567,1573],{},[330,1553,1554,1556],{},[449,1555,47],{"href":48}," — the model the configuration tunes, and the runtime's full operational surface.",[330,1558,1559,1561,1562,471,1564,1566],{},[449,1560,28],{"href":29}," — a complete ",[338,1563,340],{},[338,1565,1170],{}," block in context.",[330,1568,1569,1572],{},[449,1570,1571],{"href":78},"Error-handling model"," — the failure policies you configure and what each one does.",[330,1574,1575,1577,1578,1580],{},[449,1576,475],{"href":474}," — the runtime capabilities the ",[338,1579,348],{}," group switches on.",[1582,1583,1584],"style",{},"html pre.shiki code .su6XF, html code.shiki .su6XF{--shiki-default:#998418}html pre.shiki code .sYZai, html code.shiki .sYZai{--shiki-default:#999999}html pre.shiki code .spphp, html code.shiki .spphp{--shiki-default:#B56959}html pre.shiki code .sbBg2, html code.shiki .sbBg2{--shiki-default:#1E754F}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 .s-TwI, html code.shiki .s-TwI{--shiki-default:#2F798A}html pre.shiki code .s8zF2, html code.shiki .s8zF2{--shiki-default:#A0ADA0}html pre.shiki code .svycV, html code.shiki .svycV{--shiki-default:#B07D48}html pre.shiki code .suHK_, html code.shiki .suHK_{--shiki-default:#393A34}html pre.shiki code .sySUi, html code.shiki .sySUi{--shiki-default:#59873A}html pre.shiki code .si04Y, html code.shiki .si04Y{--shiki-default:#AB5959}",{"title":516,"searchDepth":534,"depth":534,"links":1586},[1587,1588,1594,1595,1596,1597],{"id":378,"depth":534,"text":379},{"id":654,"depth":534,"text":655,"children":1589},[1590,1591,1592,1593],{"id":731,"depth":547,"text":732},{"id":996,"depth":547,"text":699},{"id":1064,"depth":547,"text":1065},{"id":1143,"depth":547,"text":669},{"id":1472,"depth":534,"text":1473},{"id":1499,"depth":534,"text":1500},{"id":1531,"depth":534,"text":1532},{"id":1548,"depth":534,"text":1549},"How StoatFlow layers configuration — application.yaml, overlay files, environment variables, and programmatic overrides — the precedence order between them, and why some behaviour is adaptive rather than configured.","md",{},{"icon":75},"explanation",{"title":72,"description":1598},"pznTfZRR4xzwIuBsmdxpjX65b3BIBBnwLS8RCanbnCo",[1606,1608],{"title":67,"path":68,"stem":69,"description":1607,"icon":70,"children":-1},"How StoatFlow models time — event time vs processing time, record timestamps, watermark strategies, window closing, late-record handling, and event\u002Fprocessing-time timers.",{"title":77,"path":78,"stem":79,"description":1609,"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.",1783713352203]