[{"data":1,"prerenderedAt":2037},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fconcepts\u002Fevent-time-and-watermarks":314,"\u002Fdocs\u002Fconcepts\u002Fevent-time-and-watermarks-surround":2032},[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":67,"body":316,"description":2025,"extension":2026,"meta":2027,"navigation":2028,"pageType":2029,"path":68,"seo":2030,"stem":69,"__hash__":2031},"docs\u002F1.docs\u002F2.concepts\u002F6.event-time-and-watermarks.md",{"type":317,"value":318,"toc":2008},"minimark",[319,336,401,406,415,424,427,431,442,465,468,919,954,958,967,978,993,1000,1005,1012,1197,1200,1204,1213,1219,1233,1343,1349,1356,1433,1439,1453,1527,1541,1545,1552,1559,1565,1579,1583,1591,1613,1616,1627,1631,1646,1669,1677,1695,1699,1706,1951,1957,1961,2004],[320,321,322,323,327,328,331,332,335],"p",{},"Stream processing has to answer two different questions about time: ",[324,325,326],"em",{},"when did this event happen"," and ",[324,329,330],{},"when did the runtime see it",". This page explains how StoatFlow separates the two, how watermarks track event-time progress, and how that drives window closing, late-record handling, and timers. It describes the conceptual model and the public API; the mechanism that tracks and merges watermarks internally stays in the source — see ",[333,334,47],"a",{"href":48}," for the boundary.",[337,338,339],"tldr-panel",{},[340,341,342,354,365,372,382],"ul",{},[343,344,345,349,350,353],"li",{},[346,347,348],"strong",{},"Event time"," is when the event happened (the record timestamp, or whatever a custom extractor returns). ",[346,351,352],{},"Processing time"," is when the runtime handled it. Windowed operators reason in event time.",[343,355,356,357,360,361,364],{},"A ",[346,358,359],{},"watermark"," is the runtime's claim \"I don't expect any record earlier than time ",[324,362,363],{},"T",".\" It's tracked per source partition and merged into one global watermark for the application.",[343,366,367,368,371],{},"When the global watermark passes a window's end, the window can ",[346,369,370],{},"close"," — no more in-time records will arrive for it.",[343,373,374,377,378,381],{},[346,375,376],{},"Late records"," (event time older than the watermark) are handled by the window's ",[346,379,380],{},"grace period",": a record that lands while its window is still inside grace is folded into the aggregate; once the window's grace has elapsed, late records for it are dropped.",[343,383,384,385,389,390,389,393,396,397,400],{},"Public watermark strategies: ",[386,387,388],"code",{},"forBoundedOutOfOrderness",", ",[386,391,392],{},"forMonotonousTimestamps",[386,394,395],{},"noWatermarks"," — set per source via ",[386,398,399],{},"Consumed.withWatermarkStrategy(...)",".",[402,403,405],"h2",{"id":404},"event-time-vs-processing-time","Event time vs processing time",[320,407,408,410,411,414],{},[346,409,352],{}," is wall-clock time on the machine running the topology — ",[324,412,413],{},"when the runtime got around to this record",". It's trivially available and monotonic, but it's not reproducible: re-run the same input and the processing-time boundaries land differently, because they depend on broker latency, network buffering, and how busy the runtime was. Two records produced milliseconds apart can be processed seconds apart if a batch sat in a buffer.",[320,416,417,419,420,423],{},[346,418,348],{}," is ",[324,421,422],{},"when the event actually happened",", carried on the record itself. It's stable across replays — the same input always produces the same windowed results, regardless of how fast or slow the runtime consumed it. That reproducibility is why every stateful operator that cares about time — windowed aggregations, session windows, time-bounded joins — reasons in event time.",[320,425,426],{},"The runtime needs both. Processing time drives wall-clock timers and \"do this every N seconds\" punctuation. Event time drives windows and late-record decisions.",[402,428,430],{"id":429},"the-record-timestamp-and-custom-extraction","The record timestamp and custom extraction",[320,432,433,434,437,438,441],{},"Every input record carries a timestamp. By default that's the ",[346,435,436],{},"Kafka record timestamp"," — set by the producer when it sent the record, or by the broker on append, depending on the topic's ",[386,439,440],{},"message.timestamp.type",". For many topologies that default is exactly right and you configure nothing.",[320,443,444,445,448,449,452,453,456,457,460,461,464],{},"When the meaningful event time lives ",[324,446,447],{},"inside the value"," — an ",[386,450,451],{},"eventTime"," field on your domain object, a timestamp parsed from a log line — supply a custom assigner. In StoatFlow this is done on the ",[346,454,455],{},"watermark strategy"," (the strategy combines timestamp extraction and watermark generation into one object, the same way Flink's ",[386,458,459],{},"WatermarkStrategy"," does). The KS-style ",[386,462,463],{},"TimestampExtractor"," functional interface is also supported and adapts onto a strategy internally.",[320,466,467],{},"The assigner receives the key, the value, and the Kafka record timestamp, and returns the event time in epoch milliseconds:",[469,470,472,673],"code-tabs",{"group":471},"lang",[473,474,480],"pre",{"className":475,"code":476,"filename":477,"language":478,"meta":479,"style":479},"language-kotlin shiki shiki-themes vitesse-light","import io.stoatflow.core.topology.Consumed\nimport io.stoatflow.core.watermark.WatermarkStrategy\nimport java.time.Duration\n\n\u002F\u002F Event time comes from a field on the value; allow 30s of out-of-orderness.\nval strategy = WatermarkStrategy\n    .forBoundedOutOfOrderness\u003CString, Order>(Duration.ofSeconds(30))\n    .withTimestampAssigner { _, value, _ -> value?.eventTime ?: 0L }\n\nbuilder.stream(\n    \"orders\",\n    Consumed.with(Serdes.String(), orderSerde)\n        .withWatermarkStrategy(strategy),\n)\n","Kotlin","kotlin","",[386,481,482,495,503,511,518,525,542,578,611,616,628,638,655,667],{"__ignoreMap":479},[483,484,487,491],"span",{"class":485,"line":486},"line",1,[483,488,490],{"class":489},"sbBg2","import",[483,492,494],{"class":493},"sySUi"," io.stoatflow.core.topology.Consumed\n",[483,496,498,500],{"class":485,"line":497},2,[483,499,490],{"class":489},[483,501,502],{"class":493}," io.stoatflow.core.watermark.WatermarkStrategy\n",[483,504,506,508],{"class":485,"line":505},3,[483,507,490],{"class":489},[483,509,510],{"class":493}," java.time.Duration\n",[483,512,514],{"class":485,"line":513},4,[483,515,517],{"emptyLinePlaceholder":516},true,"\n",[483,519,521],{"class":485,"line":520},5,[483,522,524],{"class":523},"s8zF2","\u002F\u002F Event time comes from a field on the value; allow 30s of out-of-orderness.\n",[483,526,528,531,535,539],{"class":485,"line":527},6,[483,529,530],{"class":489},"val",[483,532,534],{"class":533},"suHK_"," strategy ",[483,536,538],{"class":537},"sYZai","=",[483,540,541],{"class":533}," WatermarkStrategy\n",[483,543,545,548,550,553,557,559,562,565,568,571,575],{"class":485,"line":544},7,[483,546,547],{"class":533},"    .",[483,549,388],{"class":493},[483,551,552],{"class":533},"\u003C",[483,554,556],{"class":555},"sUxyF","String",[483,558,389],{"class":533},[483,560,561],{"class":555},"Order",[483,563,564],{"class":533},">(Duration.",[483,566,567],{"class":493},"ofSeconds",[483,569,570],{"class":533},"(",[483,572,574],{"class":573},"s-TwI","30",[483,576,577],{"class":533},"))\n",[483,579,581,583,586,589,593,596,599,602,605,608],{"class":485,"line":580},8,[483,582,547],{"class":533},[483,584,585],{"class":493},"withTimestampAssigner",[483,587,588],{"class":533}," { _, ",[483,590,592],{"class":591},"si04Y","value",[483,594,595],{"class":533},", _ ",[483,597,598],{"class":537},"->",[483,600,601],{"class":591}," value",[483,603,604],{"class":533},"?.eventTime ?: ",[483,606,607],{"class":573},"0L",[483,609,610],{"class":533}," }\n",[483,612,614],{"class":485,"line":613},9,[483,615,517],{"emptyLinePlaceholder":516},[483,617,619,622,625],{"class":485,"line":618},10,[483,620,621],{"class":533},"builder.",[483,623,624],{"class":493},"stream",[483,626,627],{"class":533},"(\n",[483,629,631,635],{"class":485,"line":630},11,[483,632,634],{"class":633},"spphp","    \"orders\"",[483,636,637],{"class":533},",\n",[483,639,641,644,647,650,652],{"class":485,"line":640},12,[483,642,643],{"class":533},"    Consumed.",[483,645,646],{"class":493},"with",[483,648,649],{"class":533},"(Serdes.",[483,651,556],{"class":493},[483,653,654],{"class":533},"(), orderSerde)\n",[483,656,658,661,664],{"class":485,"line":657},13,[483,659,660],{"class":533},"        .",[483,662,663],{"class":493},"withWatermarkStrategy",[483,665,666],{"class":533},"(strategy),\n",[483,668,670],{"class":485,"line":669},14,[483,671,672],{"class":533},")\n",[473,674,679],{"className":675,"code":676,"filename":677,"language":678,"meta":479,"style":479},"language-java shiki shiki-themes vitesse-light","import io.stoatflow.core.topology.Consumed;\nimport io.stoatflow.core.watermark.WatermarkStrategy;\nimport java.time.Duration;\n\n\u002F\u002F Event time comes from a field on the value; allow 30s of out-of-orderness.\nWatermarkStrategy\u003CString, Order> strategy = WatermarkStrategy\n    .\u003CString, Order>forBoundedOutOfOrderness(Duration.ofSeconds(30))\n    .withTimestampAssigner((key, value) -> value.getEventTime());\n\nbuilder.stream(\n    \"orders\",\n    Consumed.with(Serdes.String(), orderSerde)\n        .withWatermarkStrategy(strategy));\n","Java","java",[386,680,681,711,735,754,758,762,788,818,850,854,865,879,905],{"__ignoreMap":479},[483,682,683,685,688,690,693,695,698,700,703,705,708],{"class":485,"line":486},[483,684,490],{"class":489},[483,686,687],{"class":533}," io",[483,689,400],{"class":537},[483,691,692],{"class":533},"stoatflow",[483,694,400],{"class":537},[483,696,697],{"class":533},"core",[483,699,400],{"class":537},[483,701,702],{"class":533},"topology",[483,704,400],{"class":537},[483,706,707],{"class":533},"Consumed",[483,709,710],{"class":537},";\n",[483,712,713,715,717,719,721,723,725,727,729,731,733],{"class":485,"line":497},[483,714,490],{"class":489},[483,716,687],{"class":533},[483,718,400],{"class":537},[483,720,692],{"class":533},[483,722,400],{"class":537},[483,724,697],{"class":533},[483,726,400],{"class":537},[483,728,359],{"class":533},[483,730,400],{"class":537},[483,732,459],{"class":533},[483,734,710],{"class":537},[483,736,737,739,742,744,747,749,752],{"class":485,"line":505},[483,738,490],{"class":489},[483,740,741],{"class":533}," java",[483,743,400],{"class":537},[483,745,746],{"class":533},"time",[483,748,400],{"class":537},[483,750,751],{"class":533},"Duration",[483,753,710],{"class":537},[483,755,756],{"class":485,"line":513},[483,757,517],{"emptyLinePlaceholder":516},[483,759,760],{"class":485,"line":520},[483,761,524],{"class":523},[483,763,764,766,768,770,773,776,779,783,786],{"class":485,"line":527},[483,765,459],{"class":533},[483,767,552],{"class":537},[483,769,556],{"class":591},[483,771,772],{"class":537},",",[483,774,775],{"class":591}," Order",[483,777,778],{"class":537},">",[483,780,782],{"class":781},"svycV"," strategy",[483,784,785],{"class":537}," =",[483,787,541],{"class":533},[483,789,790,792,794,796,798,800,802,804,806,808,810,812,814,816],{"class":485,"line":544},[483,791,547],{"class":537},[483,793,552],{"class":591},[483,795,556],{"class":533},[483,797,772],{"class":537},[483,799,775],{"class":533},[483,801,778],{"class":591},[483,803,388],{"class":493},[483,805,570],{"class":537},[483,807,751],{"class":781},[483,809,400],{"class":537},[483,811,567],{"class":493},[483,813,570],{"class":537},[483,815,574],{"class":573},[483,817,577],{"class":537},[483,819,820,822,824,827,830,832,834,837,840,842,844,847],{"class":485,"line":580},[483,821,547],{"class":537},[483,823,585],{"class":493},[483,825,826],{"class":537},"((",[483,828,829],{"class":533},"key",[483,831,772],{"class":537},[483,833,601],{"class":533},[483,835,836],{"class":537},")",[483,838,839],{"class":537}," ->",[483,841,601],{"class":781},[483,843,400],{"class":537},[483,845,846],{"class":493},"getEventTime",[483,848,849],{"class":537},"());\n",[483,851,852],{"class":485,"line":613},[483,853,517],{"emptyLinePlaceholder":516},[483,855,856,859,861,863],{"class":485,"line":618},[483,857,858],{"class":781},"builder",[483,860,400],{"class":537},[483,862,624],{"class":493},[483,864,627],{"class":537},[483,866,867,871,874,877],{"class":485,"line":630},[483,868,870],{"class":869},"sSP4y","    \"",[483,872,873],{"class":633},"orders",[483,875,876],{"class":869},"\"",[483,878,637],{"class":537},[483,880,881,884,886,888,890,893,895,897,900,903],{"class":485,"line":640},[483,882,883],{"class":781},"    Consumed",[483,885,400],{"class":537},[483,887,646],{"class":493},[483,889,570],{"class":537},[483,891,892],{"class":781},"Serdes",[483,894,400],{"class":537},[483,896,556],{"class":493},[483,898,899],{"class":537},"(),",[483,901,902],{"class":533}," orderSerde",[483,904,672],{"class":537},[483,906,907,909,911,913,916],{"class":485,"line":657},[483,908,660],{"class":537},[483,910,663],{"class":493},[483,912,570],{"class":537},[483,914,915],{"class":533},"strategy",[483,917,918],{"class":537},"));\n",[920,921,924],"callout",{"color":922,"icon":923},"info","i-lucide-info",[320,925,926,927,930,931,933,934,936,937,940,941,944,945,948,949,953],{},"The assigner may be called with a ",[386,928,929],{},"null"," value (tombstones) and a ",[386,932,929],{}," key (records with no key). Guard for it — return a sensible fallback rather than dereferencing. The Java ",[386,935,585],{}," overload shown above takes a ",[386,938,939],{},"(key, value)"," ",[386,942,943],{},"BiFunction","; a single-argument ",[386,946,947],{},"Function\u003CV, Long>"," overload is also available when only the value matters (used in the ",[333,950,952],{"href":951},"#examples","e-commerce example",").",[402,955,957],{"id":956},"watermarks-the-no-earlier-than-t-claim","Watermarks: the \"no earlier than T\" claim",[320,959,356,960,962,963,966],{},[346,961,359],{}," is a claim the runtime makes about event-time progress: ",[324,964,965],{},"\"I do not expect any further records with an event time earlier than T.\""," It's not a record and not user data — it's a moving low-water mark that tells time-sensitive operators when it's safe to act.",[320,968,969,970,973,974,977],{},"Watermarks are derived from the event timestamps the runtime observes. As records flow in with increasing event times, the watermark advances behind them. How far behind depends on the strategy (next section). Crucially, a watermark is a ",[324,971,972],{},"claim",", not a guarantee: a record older than the current watermark can still physically arrive — that's a ",[346,975,976],{},"late record",", and whether it still counts toward its window depends on that window's grace period (see below).",[320,979,980,981,984,985,988,989,992],{},"Watermarks are tracked ",[346,982,983],{},"per source partition"," — each partition has its own view of how far its event time has progressed. The runtime combines those per-partition views into a ",[346,986,987],{},"single global watermark"," for the whole application. Because a StoatFlow app is exactly one instance (see ",[333,990,47],{"href":991},"\u002Fdocs\u002Fconcepts\u002Farchitecture#the-single-instance-model","), there is no distributed watermark-coordination protocol to configure — the global watermark is computed in-process. The global watermark advances together with the commit barrier, so windowed results are committed alongside the watermark progress that produced them; recovery sees a consistent snapshot of \"what the app has seen up to.\"",[320,994,995,996,999],{},"The current per-partition watermark state is observable at runtime on the ",[386,997,998],{},"\u002Fwatermarks"," admin endpoint.",[1001,1002,1004],"h3",{"id":1003},"idleness","Idleness",[320,1006,1007,1008,1011],{},"If one partition stops receiving records — a quiet partition, an off-hours source — its event time stops advancing, which would hold the global watermark back and stall windows that depend on the busier partitions. A strategy configured ",[386,1009,1010],{},".withIdleness(timeout)"," marks a partition idle after it goes quiet for that long, excluding it from the global watermark so the rest of the application keeps making event-time progress.",[469,1013,1014,1095],{"group":471},[473,1015,1017],{"className":475,"code":1016,"filename":477,"language":478,"meta":479,"style":479},"val strategy = WatermarkStrategy\n    .forBoundedOutOfOrderness\u003CString, Order>(Duration.ofSeconds(30))\n    .withTimestampAssigner { _, value, _ -> value?.eventTime ?: 0L }\n    .withIdleness(Duration.ofMinutes(2))\n",[386,1018,1019,1029,1053,1075],{"__ignoreMap":479},[483,1020,1021,1023,1025,1027],{"class":485,"line":486},[483,1022,530],{"class":489},[483,1024,534],{"class":533},[483,1026,538],{"class":537},[483,1028,541],{"class":533},[483,1030,1031,1033,1035,1037,1039,1041,1043,1045,1047,1049,1051],{"class":485,"line":497},[483,1032,547],{"class":533},[483,1034,388],{"class":493},[483,1036,552],{"class":533},[483,1038,556],{"class":555},[483,1040,389],{"class":533},[483,1042,561],{"class":555},[483,1044,564],{"class":533},[483,1046,567],{"class":493},[483,1048,570],{"class":533},[483,1050,574],{"class":573},[483,1052,577],{"class":533},[483,1054,1055,1057,1059,1061,1063,1065,1067,1069,1071,1073],{"class":485,"line":505},[483,1056,547],{"class":533},[483,1058,585],{"class":493},[483,1060,588],{"class":533},[483,1062,592],{"class":591},[483,1064,595],{"class":533},[483,1066,598],{"class":537},[483,1068,601],{"class":591},[483,1070,604],{"class":533},[483,1072,607],{"class":573},[483,1074,610],{"class":533},[483,1076,1077,1079,1082,1085,1088,1090,1093],{"class":485,"line":513},[483,1078,547],{"class":533},[483,1080,1081],{"class":493},"withIdleness",[483,1083,1084],{"class":533},"(Duration.",[483,1086,1087],{"class":493},"ofMinutes",[483,1089,570],{"class":533},[483,1091,1092],{"class":573},"2",[483,1094,577],{"class":533},[473,1096,1098],{"className":675,"code":1097,"filename":677,"language":678,"meta":479,"style":479},"WatermarkStrategy\u003CString, Order> strategy = WatermarkStrategy\n    .\u003CString, Order>forBoundedOutOfOrderness(Duration.ofSeconds(30))\n    .withTimestampAssigner((key, value) -> value.getEventTime())\n    .withIdleness(Duration.ofMinutes(2));\n",[386,1099,1100,1120,1150,1177],{"__ignoreMap":479},[483,1101,1102,1104,1106,1108,1110,1112,1114,1116,1118],{"class":485,"line":486},[483,1103,459],{"class":533},[483,1105,552],{"class":537},[483,1107,556],{"class":591},[483,1109,772],{"class":537},[483,1111,775],{"class":591},[483,1113,778],{"class":537},[483,1115,782],{"class":781},[483,1117,785],{"class":537},[483,1119,541],{"class":533},[483,1121,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148],{"class":485,"line":497},[483,1123,547],{"class":537},[483,1125,552],{"class":591},[483,1127,556],{"class":533},[483,1129,772],{"class":537},[483,1131,775],{"class":533},[483,1133,778],{"class":591},[483,1135,388],{"class":493},[483,1137,570],{"class":537},[483,1139,751],{"class":781},[483,1141,400],{"class":537},[483,1143,567],{"class":493},[483,1145,570],{"class":537},[483,1147,574],{"class":573},[483,1149,577],{"class":537},[483,1151,1152,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172,1174],{"class":485,"line":505},[483,1153,547],{"class":537},[483,1155,585],{"class":493},[483,1157,826],{"class":537},[483,1159,829],{"class":533},[483,1161,772],{"class":537},[483,1163,601],{"class":533},[483,1165,836],{"class":537},[483,1167,839],{"class":537},[483,1169,601],{"class":781},[483,1171,400],{"class":537},[483,1173,846],{"class":493},[483,1175,1176],{"class":537},"())\n",[483,1178,1179,1181,1183,1185,1187,1189,1191,1193,1195],{"class":485,"line":513},[483,1180,547],{"class":537},[483,1182,1081],{"class":493},[483,1184,570],{"class":537},[483,1186,751],{"class":781},[483,1188,400],{"class":537},[483,1190,1087],{"class":493},[483,1192,570],{"class":537},[483,1194,1092],{"class":573},[483,1196,918],{"class":537},[320,1198,1199],{},"The idleness timeout must be positive — a zero or negative duration is rejected.",[402,1201,1203],{"id":1202},"the-watermark-strategies","The watermark strategies",[320,1205,1206,1207,1209,1210,1212],{},"StoatFlow ships three watermark strategies, all created from ",[386,1208,459],{}," factory methods and attached per source topic via ",[386,1211,399],{},". They're modelled on Flink's strategies.",[1001,1214,1216],{"id":1215},"forboundedoutofordernessmaxoutoforderness",[386,1217,1218],{},"forBoundedOutOfOrderness(maxOutOfOrderness)",[320,1220,1221,1222,1225,1226,1229,1230,1232],{},"The common case. Events may arrive out of order by up to ",[386,1223,1224],{},"maxOutOfOrderness"," before being considered late. The watermark is ",[386,1227,1228],{},"maxObservedTimestamp - maxOutOfOrderness"," — the larger the tolerance you set, the longer windows stay open for stragglers, at the cost of holding results back longer. ",[386,1231,1224],{}," must not be negative.",[469,1234,1235,1284],{"group":471},[473,1236,1238],{"className":475,"code":1237,"filename":477,"language":478,"meta":479,"style":479},"Consumed.with(Serdes.String(), orderSerde)\n    .withWatermarkStrategy(\n        WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(30)),\n    )\n",[386,1239,1240,1253,1261,1279],{"__ignoreMap":479},[483,1241,1242,1245,1247,1249,1251],{"class":485,"line":486},[483,1243,1244],{"class":533},"Consumed.",[483,1246,646],{"class":493},[483,1248,649],{"class":533},[483,1250,556],{"class":493},[483,1252,654],{"class":533},[483,1254,1255,1257,1259],{"class":485,"line":497},[483,1256,547],{"class":533},[483,1258,663],{"class":493},[483,1260,627],{"class":533},[483,1262,1263,1266,1268,1270,1272,1274,1276],{"class":485,"line":505},[483,1264,1265],{"class":533},"        WatermarkStrategy.",[483,1267,388],{"class":493},[483,1269,1084],{"class":533},[483,1271,567],{"class":493},[483,1273,570],{"class":533},[483,1275,574],{"class":573},[483,1277,1278],{"class":533},")),\n",[483,1280,1281],{"class":485,"line":513},[483,1282,1283],{"class":533},"    )\n",[473,1285,1287],{"className":675,"code":1286,"filename":677,"language":678,"meta":479,"style":479},"Consumed.with(Serdes.String(), orderSerde)\n    .withWatermarkStrategy(\n        WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(30)));\n",[386,1288,1289,1311,1319],{"__ignoreMap":479},[483,1290,1291,1293,1295,1297,1299,1301,1303,1305,1307,1309],{"class":485,"line":486},[483,1292,707],{"class":781},[483,1294,400],{"class":537},[483,1296,646],{"class":493},[483,1298,570],{"class":537},[483,1300,892],{"class":781},[483,1302,400],{"class":537},[483,1304,556],{"class":493},[483,1306,899],{"class":537},[483,1308,902],{"class":533},[483,1310,672],{"class":537},[483,1312,1313,1315,1317],{"class":485,"line":497},[483,1314,547],{"class":537},[483,1316,663],{"class":493},[483,1318,627],{"class":537},[483,1320,1321,1324,1326,1328,1330,1332,1334,1336,1338,1340],{"class":485,"line":505},[483,1322,1323],{"class":781},"        WatermarkStrategy",[483,1325,400],{"class":537},[483,1327,388],{"class":493},[483,1329,570],{"class":537},[483,1331,751],{"class":781},[483,1333,400],{"class":537},[483,1335,567],{"class":493},[483,1337,570],{"class":537},[483,1339,574],{"class":573},[483,1341,1342],{"class":537},")));\n",[1001,1344,1346],{"id":1345},"formonotonoustimestamps",[386,1347,1348],{},"forMonotonousTimestamps()",[320,1350,1351,1352,1355],{},"Use when events are known to arrive in strict timestamp order — a single-partition topic, log-append-time topics where the broker assigns timestamps, or events pre-sorted before production. It's equivalent to ",[386,1353,1354],{},"forBoundedOutOfOrderness(Duration.ZERO)"," with slightly less overhead. Any record whose timestamp goes backwards is treated as late.",[469,1357,1358,1389],{"group":471},[473,1359,1361],{"className":475,"code":1360,"filename":477,"language":478,"meta":479,"style":479},"Consumed.with(Serdes.String(), eventSerde)\n    .withWatermarkStrategy(WatermarkStrategy.forMonotonousTimestamps())\n",[386,1362,1363,1376],{"__ignoreMap":479},[483,1364,1365,1367,1369,1371,1373],{"class":485,"line":486},[483,1366,1244],{"class":533},[483,1368,646],{"class":493},[483,1370,649],{"class":533},[483,1372,556],{"class":493},[483,1374,1375],{"class":533},"(), eventSerde)\n",[483,1377,1378,1380,1382,1385,1387],{"class":485,"line":497},[483,1379,547],{"class":533},[483,1381,663],{"class":493},[483,1383,1384],{"class":533},"(WatermarkStrategy.",[483,1386,392],{"class":493},[483,1388,1176],{"class":533},[473,1390,1392],{"className":675,"code":1391,"filename":677,"language":678,"meta":479,"style":479},"Consumed.with(Serdes.String(), eventSerde)\n    .withWatermarkStrategy(WatermarkStrategy.forMonotonousTimestamps());\n",[386,1393,1394,1417],{"__ignoreMap":479},[483,1395,1396,1398,1400,1402,1404,1406,1408,1410,1412,1415],{"class":485,"line":486},[483,1397,707],{"class":781},[483,1399,400],{"class":537},[483,1401,646],{"class":493},[483,1403,570],{"class":537},[483,1405,892],{"class":781},[483,1407,400],{"class":537},[483,1409,556],{"class":493},[483,1411,899],{"class":537},[483,1413,1414],{"class":533}," eventSerde",[483,1416,672],{"class":537},[483,1418,1419,1421,1423,1425,1427,1429,1431],{"class":485,"line":497},[483,1420,547],{"class":537},[483,1422,663],{"class":493},[483,1424,570],{"class":537},[483,1426,459],{"class":781},[483,1428,400],{"class":537},[483,1430,392],{"class":493},[483,1432,849],{"class":537},[1001,1434,1436],{"id":1435},"nowatermarks",[386,1437,1438],{},"noWatermarks()",[320,1440,1441,1442,1445,1446,327,1449,1452],{},"Produces no watermarks at all — the watermark stays at ",[386,1443,1444],{},"Long.MIN_VALUE"," forever. Use for processing-time-only topologies, or sources without meaningful event timestamps. The trade-off is explicit: with no watermarks, ",[346,1447,1448],{},"event-time windows never close",[346,1450,1451],{},"event-time timers never fire",". Processing-time semantics still work.",[469,1454,1455,1484],{"group":471},[473,1456,1458],{"className":475,"code":1457,"filename":477,"language":478,"meta":479,"style":479},"Consumed.with(Serdes.String(), eventSerde)\n    .withWatermarkStrategy(WatermarkStrategy.noWatermarks())\n",[386,1459,1460,1472],{"__ignoreMap":479},[483,1461,1462,1464,1466,1468,1470],{"class":485,"line":486},[483,1463,1244],{"class":533},[483,1465,646],{"class":493},[483,1467,649],{"class":533},[483,1469,556],{"class":493},[483,1471,1375],{"class":533},[483,1473,1474,1476,1478,1480,1482],{"class":485,"line":497},[483,1475,547],{"class":533},[483,1477,663],{"class":493},[483,1479,1384],{"class":533},[483,1481,395],{"class":493},[483,1483,1176],{"class":533},[473,1485,1487],{"className":675,"code":1486,"filename":677,"language":678,"meta":479,"style":479},"Consumed.with(Serdes.String(), eventSerde)\n    .withWatermarkStrategy(WatermarkStrategy.noWatermarks());\n",[386,1488,1489,1511],{"__ignoreMap":479},[483,1490,1491,1493,1495,1497,1499,1501,1503,1505,1507,1509],{"class":485,"line":486},[483,1492,707],{"class":781},[483,1494,400],{"class":537},[483,1496,646],{"class":493},[483,1498,570],{"class":537},[483,1500,892],{"class":781},[483,1502,400],{"class":537},[483,1504,556],{"class":493},[483,1506,899],{"class":537},[483,1508,1414],{"class":533},[483,1510,672],{"class":537},[483,1512,1513,1515,1517,1519,1521,1523,1525],{"class":485,"line":497},[483,1514,547],{"class":537},[483,1516,663],{"class":493},[483,1518,570],{"class":537},[483,1520,459],{"class":781},[483,1522,400],{"class":537},[483,1524,395],{"class":493},[483,1526,849],{"class":537},[320,1528,1529,1530,327,1533,1536,1537,1540],{},"All three accept ",[386,1531,1532],{},".withTimestampAssigner(...)",[386,1534,1535],{},".withIdleness(...)",", plus Flink-style ",[386,1538,1539],{},".withWatermarkAlignment(group, maxDrift)"," for multi-source topologies where one source's event time must not run too far ahead of the others in the same alignment group. When you set neither a strategy nor an assigner, the source uses the Kafka record timestamp as event time.",[402,1542,1544],{"id":1543},"window-closing","Window closing",[320,1546,1547,1548,1551],{},"A windowed operator — a tumbling\u002Fhopping ",[386,1549,1550],{},"TimeWindows"," aggregation, a session window, a sliding window — groups records into windows by their event time. The runtime can't emit a window's final result until it's confident no more in-time records will land in it. That confidence comes from the watermark.",[320,1553,1554,1555,1558],{},"When the ",[346,1556,1557],{},"global watermark passes a window's end",", the runtime knows the window can close: any record that would belong inside it would have an event time the watermark has already moved past. At that point the window's result is finalised. Until then, the window stays open and accumulates.",[320,1560,1561,1562,1564],{},"This is why watermark choice has a direct, visible effect on latency: a 30-second ",[386,1563,1224],{}," means a window's result is held back at least 30 seconds past its end before it can close. Tighter tolerance closes windows sooner but treats more stragglers as late.",[320,1566,1567,1568,1570,1571,1574,1575,1578],{},"Window specifications can carry a ",[346,1569,380],{}," of their own (for example ",[386,1572,1573],{},"TimeWindows.ofSizeAndGrace(size, grace)","), which keeps a window open for late records for an extra interval ",[324,1576,1577],{},"after"," its end before it's permanently closed — see the next section.",[402,1580,1582],{"id":1581},"late-records-and-grace","Late records and grace",[320,1584,356,1585,1587,1588,1590],{},[346,1586,976],{}," is one whose event time is older than the current watermark — it arrived after the runtime had already claimed not to expect anything that old. How a windowed operator responds is governed by the ",[346,1589,380],{}," on the window the record belongs to:",[340,1592,1593,1603],{},[343,1594,1595,1598,1599,1602],{},[346,1596,1597],{},"Within grace"," — if the late record's window is still inside its configured grace window, the record is folded into the aggregate and an updated result is emitted. Grace is set on the window specification (for example ",[386,1600,1601],{},"TimeWindows.ofSizeAndGrace(...)","), and bounds how long after a window's end late records are still accepted.",[343,1604,1605,1608,1609,1612],{},[346,1606,1607],{},"Past grace"," — once a window's end plus its grace has elapsed, the window is closed and finalised; any later record that would have belonged to it is ",[346,1610,1611],{},"dropped"," before it can change the result.",[320,1614,1615],{},"Grace is the knob that trades completeness against finality: a longer grace catches more stragglers and corrects more aggregates, but holds the window's state longer and delays the point at which the result is truly final. A window with no grace closes as soon as the watermark passes its end, dropping every straggler.",[320,1617,1618,1619,1622,1623,1626],{},"A past-grace drop is ",[346,1620,1621],{},"not"," separately metered: a record discarded because its window has already closed produces no dedicated metric or callback. The runtime does count late ",[324,1624,1625],{},"arrivals"," — records whose event time is already behind the watermark — via a debug-level counter, but that measures arrivals, not grace-exceeded drops: many counted arrivals are still folded into open windows within grace, and a window that closes with no further records produces no drop signal at all. If knowing how often stragglers are discarded matters to you, account for it in your own logic rather than relying on a built-in drop metric.",[402,1628,1630],{"id":1629},"event-time-and-processing-time-timers","Event-time and processing-time timers",[320,1632,1633,1634,1637,1638,1641,1642,1645],{},"The DSL's windowed operators cover the common time-driven patterns declaratively. For logic that has to fire on a schedule independent of incoming records — flush a buffer at a deadline, expire an entry, emit a heartbeat — the ",[333,1635,1636],{"href":136},"Processor API"," lets a custom ",[386,1639,1640],{},"Processor"," register ",[346,1643,1644],{},"timers",":",[340,1647,1648,1659],{},[343,1649,1650,1653,1654,1656,1657,400],{},[346,1651,1652],{},"Event-time timers"," fire when the ",[346,1655,359],{}," advances past a registered moment. They're driven by event-time progress, so they respect the same out-of-order tolerance as windows — and, like windows, they never fire under ",[386,1658,1438],{},[343,1660,1661,1664,1665,1668],{},[346,1662,1663],{},"Processing-time timers"," fire when ",[346,1666,1667],{},"wall-clock"," time passes a registered moment, regardless of whether any records are flowing.",[320,1670,1671,1672,1674,1675,400],{},"Both deliver a callback with full state-store access in the same epoch-aligned context as record processing, so anything a timer writes commits atomically with the surrounding work (see ",[333,1673,52],{"href":53},"). The registration API and callback signatures are covered in ",[333,1676,1636],{"href":136},[320,1678,1679,1680,1683,1684,1686,1687,1690,1691,1694],{},"For a ",[324,1681,1682],{},"topology-level"," periodic source — generating records on an interval or a cron schedule without consuming from Kafka — see ",[333,1685,140],{"href":141},", which can be driven by either ",[386,1688,1689],{},"STREAM_TIME"," (event time) or ",[386,1692,1693],{},"WALL_CLOCK_TIME"," (processing time).",[402,1696,1698],{"id":1697},"examples","Examples",[320,1700,1701,1702,1705],{},"The e-commerce daily-customer-behaviour example (under the project's ",[386,1703,1704],{},"examples\u002Fecommerce-daily-customer-behaviour"," module) wires custom event-time extraction into a windowed aggregation. Each source attaches a strategy that pulls event time from the record's own timestamp field, with a daily window that carries a grace period for late arrivals:",[473,1707,1709],{"className":675,"code":1708,"language":678,"meta":479,"style":479},"\u002F\u002F Custom timestamp extraction off the value (single-arg Function overload)\nWatermarkStrategy\u003CString, WebActivityEvent> webActivityWatermark = WatermarkStrategy\n    .\u003CString, WebActivityEvent>forBoundedOutOfOrderness(Duration.ZERO)\n    .withTimestampAssigner(v -> toUtcFromLocal(v.timestamp(), LOCAL_ZONE_ID).toEpochMilli());\n\nKStream\u003CString, WebActivityEvent> webActivity = builder.stream(\n    WEB_ACTIVITY_SOURCE_TOPIC,\n    Consumed.with(stringSerde, webActivityEventSerde)\n        .withWatermarkStrategy(webActivityWatermark)\n        .withName(\"web-activity-source\"));\n\n\u002F\u002F Daily windows with a 15-minute grace period for late events\nTimeWindows dailyWindows = TimeWindows.ofSizeAndGrace(Duration.ofDays(1), Duration.ofMinutes(15));\n",[386,1710,1711,1716,1738,1765,1803,1807,1836,1843,1863,1876,1894,1898,1903],{"__ignoreMap":479},[483,1712,1713],{"class":485,"line":486},[483,1714,1715],{"class":523},"\u002F\u002F Custom timestamp extraction off the value (single-arg Function overload)\n",[483,1717,1718,1720,1722,1724,1726,1729,1731,1734,1736],{"class":485,"line":497},[483,1719,459],{"class":533},[483,1721,552],{"class":537},[483,1723,556],{"class":591},[483,1725,772],{"class":537},[483,1727,1728],{"class":591}," WebActivityEvent",[483,1730,778],{"class":537},[483,1732,1733],{"class":781}," webActivityWatermark",[483,1735,785],{"class":537},[483,1737,541],{"class":533},[483,1739,1740,1742,1744,1746,1748,1750,1752,1754,1756,1758,1760,1763],{"class":485,"line":505},[483,1741,547],{"class":537},[483,1743,552],{"class":591},[483,1745,556],{"class":533},[483,1747,772],{"class":537},[483,1749,1728],{"class":533},[483,1751,778],{"class":591},[483,1753,388],{"class":493},[483,1755,570],{"class":537},[483,1757,751],{"class":781},[483,1759,400],{"class":537},[483,1761,1762],{"class":781},"ZERO",[483,1764,672],{"class":537},[483,1766,1767,1769,1771,1773,1776,1778,1781,1783,1786,1788,1791,1793,1796,1798,1801],{"class":485,"line":513},[483,1768,547],{"class":537},[483,1770,585],{"class":493},[483,1772,570],{"class":537},[483,1774,1775],{"class":533},"v ",[483,1777,598],{"class":537},[483,1779,1780],{"class":493}," toUtcFromLocal",[483,1782,570],{"class":537},[483,1784,1785],{"class":781},"v",[483,1787,400],{"class":537},[483,1789,1790],{"class":493},"timestamp",[483,1792,899],{"class":537},[483,1794,1795],{"class":533}," LOCAL_ZONE_ID",[483,1797,953],{"class":537},[483,1799,1800],{"class":493},"toEpochMilli",[483,1802,849],{"class":537},[483,1804,1805],{"class":485,"line":520},[483,1806,517],{"emptyLinePlaceholder":516},[483,1808,1809,1812,1814,1816,1818,1820,1822,1825,1827,1830,1832,1834],{"class":485,"line":527},[483,1810,1811],{"class":533},"KStream",[483,1813,552],{"class":537},[483,1815,556],{"class":591},[483,1817,772],{"class":537},[483,1819,1728],{"class":591},[483,1821,778],{"class":537},[483,1823,1824],{"class":781}," webActivity",[483,1826,785],{"class":537},[483,1828,1829],{"class":781}," builder",[483,1831,400],{"class":537},[483,1833,624],{"class":493},[483,1835,627],{"class":537},[483,1837,1838,1841],{"class":485,"line":544},[483,1839,1840],{"class":533},"    WEB_ACTIVITY_SOURCE_TOPIC",[483,1842,637],{"class":537},[483,1844,1845,1847,1849,1851,1853,1856,1858,1861],{"class":485,"line":580},[483,1846,883],{"class":781},[483,1848,400],{"class":537},[483,1850,646],{"class":493},[483,1852,570],{"class":537},[483,1854,1855],{"class":533},"stringSerde",[483,1857,772],{"class":537},[483,1859,1860],{"class":533}," webActivityEventSerde",[483,1862,672],{"class":537},[483,1864,1865,1867,1869,1871,1874],{"class":485,"line":613},[483,1866,660],{"class":537},[483,1868,663],{"class":493},[483,1870,570],{"class":537},[483,1872,1873],{"class":533},"webActivityWatermark",[483,1875,672],{"class":537},[483,1877,1878,1880,1883,1885,1887,1890,1892],{"class":485,"line":618},[483,1879,660],{"class":537},[483,1881,1882],{"class":493},"withName",[483,1884,570],{"class":537},[483,1886,876],{"class":869},[483,1888,1889],{"class":633},"web-activity-source",[483,1891,876],{"class":869},[483,1893,918],{"class":537},[483,1895,1896],{"class":485,"line":630},[483,1897,517],{"emptyLinePlaceholder":516},[483,1899,1900],{"class":485,"line":640},[483,1901,1902],{"class":523},"\u002F\u002F Daily windows with a 15-minute grace period for late events\n",[483,1904,1905,1907,1910,1912,1915,1917,1920,1922,1924,1926,1929,1931,1934,1937,1940,1942,1944,1946,1949],{"class":485,"line":657},[483,1906,1550],{"class":533},[483,1908,1909],{"class":781}," dailyWindows",[483,1911,785],{"class":537},[483,1913,1914],{"class":781}," TimeWindows",[483,1916,400],{"class":537},[483,1918,1919],{"class":493},"ofSizeAndGrace",[483,1921,570],{"class":537},[483,1923,751],{"class":781},[483,1925,400],{"class":537},[483,1927,1928],{"class":493},"ofDays",[483,1930,570],{"class":537},[483,1932,1933],{"class":573},"1",[483,1935,1936],{"class":537},"),",[483,1938,1939],{"class":781}," Duration",[483,1941,400],{"class":537},[483,1943,1087],{"class":493},[483,1945,570],{"class":537},[483,1947,1948],{"class":573},"15",[483,1950,918],{"class":537},[320,1952,1953,1954,400],{},"The full source includes the windowed aggregation and a downstream join enriching each daily summary against a customer-profile ",[386,1955,1956],{},"KTable",[402,1958,1960],{"id":1959},"where-to-go-next","Where to go next",[340,1962,1963,1969,1974,1989,1994,1999],{},[343,1964,1965,1968],{},[333,1966,47],{"href":1967},"\u002Fdocs\u002Fconcepts\u002Farchitecture#event-time-and-watermarks"," — where watermark tracking sits in the engine, and the single-instance reason there's no distributed coordination",[343,1970,1971,1973],{},[333,1972,125],{"href":126}," — tumbling, hopping, session, and sliding windows; grace and emit strategies",[343,1975,1976,1978,1979,389,1982,389,1985,1988],{},[333,1977,120],{"href":121}," — ",[386,1980,1981],{},"count",[386,1983,1984],{},"reduce",[386,1986,1987],{},"aggregate"," over grouped and windowed streams",[343,1990,1991,1993],{},[333,1992,1636],{"href":136}," — registering event-time and processing-time timers",[343,1995,1996,1998],{},[333,1997,140],{"href":141}," — topology-level periodic sources on stream time or wall-clock time",[343,2000,2001,2003],{},[333,2002,52],{"href":53}," — how windowed results and watermark progress commit atomically",[2005,2006,2007],"style",{},"html pre.shiki code .sbBg2, html code.shiki .sbBg2{--shiki-default:#1E754F}html pre.shiki code .sySUi, html code.shiki .sySUi{--shiki-default:#59873A}html pre.shiki code .s8zF2, html code.shiki .s8zF2{--shiki-default:#A0ADA0}html pre.shiki code .suHK_, html code.shiki .suHK_{--shiki-default:#393A34}html pre.shiki code .sYZai, html code.shiki .sYZai{--shiki-default:#999999}html pre.shiki code .sUxyF, html code.shiki .sUxyF{--shiki-default:#2E8F82}html pre.shiki code .s-TwI, html code.shiki .s-TwI{--shiki-default:#2F798A}html pre.shiki code .si04Y, html code.shiki .si04Y{--shiki-default:#AB5959}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 .svycV, html code.shiki .svycV{--shiki-default:#B07D48}html pre.shiki code .sSP4y, html code.shiki .sSP4y{--shiki-default:#B5695977}",{"title":479,"searchDepth":497,"depth":497,"links":2009},[2010,2011,2012,2015,2020,2021,2022,2023,2024],{"id":404,"depth":497,"text":405},{"id":429,"depth":497,"text":430},{"id":956,"depth":497,"text":957,"children":2013},[2014],{"id":1003,"depth":505,"text":1004},{"id":1202,"depth":497,"text":1203,"children":2016},[2017,2018,2019],{"id":1215,"depth":505,"text":1218},{"id":1345,"depth":505,"text":1348},{"id":1435,"depth":505,"text":1438},{"id":1543,"depth":497,"text":1544},{"id":1581,"depth":497,"text":1582},{"id":1629,"depth":497,"text":1630},{"id":1697,"depth":497,"text":1698},{"id":1959,"depth":497,"text":1960},"How StoatFlow models time — event time vs processing time, record timestamps, watermark strategies, window closing, late-record handling, and event\u002Fprocessing-time timers.","md",{},{"icon":70},"explanation",{"title":67,"description":2025},"lbwxp7VnYzmVCv_KXpVmBUX-7vLd5vr78WMBKKN1B2A",[2033,2035],{"title":62,"path":63,"stem":64,"description":2034,"icon":65,"children":-1},"How StoatFlow's global state model works — why concurrent access across lanes is safe, when you need the key-lock utility, and which store types are available.",{"title":72,"path":73,"stem":74,"description":2036,"icon":75,"children":-1},"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.",1783713351975]