[{"data":1,"prerenderedAt":3589},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fbuilding\u002Fjoins":314,"\u002Fdocs\u002Fbuilding\u002Fjoins-surround":3584},[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":130,"body":316,"description":3577,"extension":3578,"meta":3579,"navigation":3580,"pageType":3581,"path":131,"seo":3582,"stem":132,"__hash__":3583},"docs\u002F1.docs\u002F3.building\u002F6.joins.md",{"type":317,"value":318,"toc":3565},"minimark",[319,346,359,445,450,457,508,524,528,538,564,1158,1167,1196,1212,1217,1222,1312,1319,1612,1616,1639,1642,1922,1977,1997,2001,2015,2018,2075,2487,2503,2507,2521,2528,2564,2580,2978,3001,3005,3012,3026,3419,3443,3447,3530,3534,3561],[320,321,322,323,327,328,327,331,327,334,337,338,341,342,345],"p",{},"A join combines records from two sources that share a key. StoatFlow's join DSL is Kafka Streams compatible: the same five join families — stream-stream, stream-table, table-table, foreign-key, and co-grouping — with the same ",[324,325,326],"code",{},"ValueJoiner",", ",[324,329,330],{},"JoinWindows",[324,332,333],{},"Joined",[324,335,336],{},"StreamJoined",", and ",[324,339,340],{},"TableJoined"," configuration types. Java code written against the KS join API compiles against StoatFlow unchanged (see the ",[343,344,301],"a",{"href":302},").",[320,347,348,349,353,354,358],{},"One structural difference is worth stating up front: because StoatFlow holds ",[343,350,352],{"href":351},"\u002Fdocs\u002Fconcepts\u002Farchitecture#state-stores-and-durability","global state in a single instance",", the two sides of a join ",[355,356,357],"strong",{},"do not have to be co-partitioned",". There is no requirement that both topics share a partition count. Re-keying for a foreign-key join happens in-memory between lanes, not through a repartition topic.",[360,361,362],"tldr-panel",{},[363,364,365,376,389,398,407,420],"ul",{},[366,367,368,371,372,375],"li",{},[355,369,370],{},"Stream-stream:"," ",[324,373,374],{},"left.join(right, joiner, JoinWindows.ofTimeDifferenceWithNoGrace(d))"," — time-windowed, both sides buffered in window stores.",[366,377,378,371,381,384,385,388],{},[355,379,380],{},"Stream-table:",[324,382,383],{},"stream.join(table, joiner)"," — point-in-time lookup; table updates do ",[355,386,387],{},"not"," re-trigger the join.",[366,390,391,371,394,397],{},[355,392,393],{},"Table-table:",[324,395,396],{},"left.join(right, joiner)"," — key-equal join; updates on either side re-emit.",[366,399,400,371,403,406],{},[355,401,402],{},"Foreign-key:",[324,404,405],{},"left.join(right, fkExtractor, joiner, TableJoined.as(...))"," — join on a key extracted from the left value (inner + left only).",[366,408,409,371,412,415,416,419],{},[355,410,411],{},"Joiner shapes:",[324,413,414],{},"ValueJoiner\u003CV1, V2, VR>"," (values only) or ",[324,417,418],{},"ValueJoinerWithKey\u003CK, V1, V2, VR>"," (key-aware).",[366,421,422,434,435,427,438,427,441,444],{},[355,423,424,427,428,427,431],{},[324,425,426],{},"inner"," \u002F ",[324,429,430],{},"left",[324,432,433],{},"outer"," map to ",[324,436,437],{},"join",[324,439,440],{},"leftJoin",[324,442,443],{},"outerJoin"," (FK joins have no outer).",[446,447,449],"h2",{"id":448},"the-joiner","The joiner",[320,451,452,453,456],{},"Every join takes a ",[355,454,455],{},"joiner"," — the function that combines a left value and a right value into the result. Two interfaces:",[458,459,460,476],"table",{},[461,462,463],"thead",{},[464,465,466,470,473],"tr",{},[467,468,469],"th",{},"Interface",[467,471,472],{},"Signature",[467,474,475],{},"When",[477,478,479,494],"tbody",{},[464,480,481,486,491],{},[482,483,484],"td",{},[324,485,414],{},[482,487,488],{},[324,489,490],{},"(left, right) -> result",[482,492,493],{},"The result depends only on the two values.",[464,495,496,500,505],{},[482,497,498],{},[324,499,418],{},[482,501,502],{},[324,503,504],{},"(key, left, right) -> result",[482,506,507],{},"The result also needs the join key. The key is read-only.",[320,509,510,511,513,514,517,518,520,521,523],{},"For ",[324,512,440],{}," the right value may be ",[324,515,516],{},"null"," (no match on the right). For ",[324,519,443],{}," either side may be ",[324,522,516],{},", but never both. Write the joiner to tolerate the nulls the join type allows.",[446,525,527],{"id":526},"stream-stream-joins","Stream-stream joins",[320,529,530,531,534,535,537],{},"A stream-stream join matches records from two streams that arrive ",[355,532,533],{},"close together in event time",". Both sides are buffered in window stores, and a record from one stream joins any record from the other whose timestamp falls inside the configured ",[324,536,330],{},". The join is symmetric — a late-arriving record on either side can produce a match.",[320,539,540,542,543,546,547,550,551,427,554,557,558,561,562,345],{},[324,541,330],{}," defines the time bounds. ",[324,544,545],{},"ofTimeDifferenceWithNoGrace(d)"," is a symmetric ±",[324,548,549],{},"d"," window; ",[324,552,553],{},"before(d)",[324,555,556],{},"after(d)"," make it asymmetric; ",[324,559,560],{},"ofTimeDifferenceAndGrace(d, grace)"," adds a grace period for late records (see ",[343,563,67],{"href":68},[565,566,568,810],"code-tabs",{"group":567},"lang",[569,570,576],"pre",{"className":571,"code":572,"filename":573,"language":574,"meta":575,"style":575},"language-kotlin shiki shiki-themes vitesse-light","import io.stoatflow.core.topology.JoinWindows\nimport io.stoatflow.core.topology.StreamJoined\nimport org.apache.kafka.common.serialization.Serdes\nimport java.time.Duration\n\n\u002F\u002F Inner: emit only when both an order and a payment land within 5 minutes\nval matched =\n    orders.join(\n        payments,\n        { order, payment -> OrderPayment(order, payment) },\n        JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5)),\n    )\n\n\u002F\u002F With serdes + a named, materialized join (recommended in production)\nval matchedConfigured =\n    orders.join(\n        payments,\n        { order, payment -> OrderPayment(order, payment) },\n        JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5)),\n        StreamJoined.with(Serdes.String(), orderSerde, paymentSerde)\n            .withStoreName(\"order-payment-join\"),\n    )\n","Kotlin","kotlin","",[324,577,578,591,599,607,615,622,629,643,654,660,675,700,706,711,717,727,736,741,752,769,787,805],{"__ignoreMap":575},[579,580,583,587],"span",{"class":581,"line":582},"line",1,[579,584,586],{"class":585},"sbBg2","import",[579,588,590],{"class":589},"sySUi"," io.stoatflow.core.topology.JoinWindows\n",[579,592,594,596],{"class":581,"line":593},2,[579,595,586],{"class":585},[579,597,598],{"class":589}," io.stoatflow.core.topology.StreamJoined\n",[579,600,602,604],{"class":581,"line":601},3,[579,603,586],{"class":585},[579,605,606],{"class":589}," org.apache.kafka.common.serialization.Serdes\n",[579,608,610,612],{"class":581,"line":609},4,[579,611,586],{"class":585},[579,613,614],{"class":589}," java.time.Duration\n",[579,616,618],{"class":581,"line":617},5,[579,619,621],{"emptyLinePlaceholder":620},true,"\n",[579,623,625],{"class":581,"line":624},6,[579,626,628],{"class":627},"s8zF2","\u002F\u002F Inner: emit only when both an order and a payment land within 5 minutes\n",[579,630,632,635,639],{"class":581,"line":631},7,[579,633,634],{"class":585},"val",[579,636,638],{"class":637},"suHK_"," matched ",[579,640,642],{"class":641},"sYZai","=\n",[579,644,646,649,651],{"class":581,"line":645},8,[579,647,648],{"class":637},"    orders.",[579,650,437],{"class":589},[579,652,653],{"class":637},"(\n",[579,655,657],{"class":581,"line":656},9,[579,658,659],{"class":637},"        payments,\n",[579,661,663,666,669,672],{"class":581,"line":662},10,[579,664,665],{"class":637},"        { order, payment ",[579,667,668],{"class":641},"->",[579,670,671],{"class":589}," OrderPayment",[579,673,674],{"class":637},"(order, payment) },\n",[579,676,678,681,684,687,690,693,697],{"class":581,"line":677},11,[579,679,680],{"class":637},"        JoinWindows.",[579,682,683],{"class":589},"ofTimeDifferenceWithNoGrace",[579,685,686],{"class":637},"(Duration.",[579,688,689],{"class":589},"ofMinutes",[579,691,692],{"class":637},"(",[579,694,696],{"class":695},"s-TwI","5",[579,698,699],{"class":637},")),\n",[579,701,703],{"class":581,"line":702},12,[579,704,705],{"class":637},"    )\n",[579,707,709],{"class":581,"line":708},13,[579,710,621],{"emptyLinePlaceholder":620},[579,712,714],{"class":581,"line":713},14,[579,715,716],{"class":627},"\u002F\u002F With serdes + a named, materialized join (recommended in production)\n",[579,718,720,722,725],{"class":581,"line":719},15,[579,721,634],{"class":585},[579,723,724],{"class":637}," matchedConfigured ",[579,726,642],{"class":641},[579,728,730,732,734],{"class":581,"line":729},16,[579,731,648],{"class":637},[579,733,437],{"class":589},[579,735,653],{"class":637},[579,737,739],{"class":581,"line":738},17,[579,740,659],{"class":637},[579,742,744,746,748,750],{"class":581,"line":743},18,[579,745,665],{"class":637},[579,747,668],{"class":641},[579,749,671],{"class":589},[579,751,674],{"class":637},[579,753,755,757,759,761,763,765,767],{"class":581,"line":754},19,[579,756,680],{"class":637},[579,758,683],{"class":589},[579,760,686],{"class":637},[579,762,689],{"class":589},[579,764,692],{"class":637},[579,766,696],{"class":695},[579,768,699],{"class":637},[579,770,772,775,778,781,784],{"class":581,"line":771},20,[579,773,774],{"class":637},"        StreamJoined.",[579,776,777],{"class":589},"with",[579,779,780],{"class":637},"(Serdes.",[579,782,783],{"class":589},"String",[579,785,786],{"class":637},"(), orderSerde, paymentSerde)\n",[579,788,790,793,796,798,802],{"class":581,"line":789},21,[579,791,792],{"class":637},"            .",[579,794,795],{"class":589},"withStoreName",[579,797,692],{"class":637},[579,799,801],{"class":800},"spphp","\"order-payment-join\"",[579,803,804],{"class":637},"),\n",[579,806,808],{"class":581,"line":807},22,[579,809,705],{"class":637},[569,811,816],{"className":812,"code":813,"filename":814,"language":815,"meta":575,"style":575},"language-java shiki shiki-themes vitesse-light","import io.stoatflow.core.topology.JoinWindows;\nimport io.stoatflow.core.topology.StreamJoined;\nimport org.apache.kafka.common.serialization.Serdes;\nimport java.time.Duration;\n\n\u002F\u002F Inner: emit only when both an order and a payment land within 5 minutes\nvar matched =\n    orders.join(\n        payments,\n        (order, payment) -> new OrderPayment(order, payment),\n        JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5)));\n\n\u002F\u002F With serdes + a named, materialized join (recommended in production)\nvar matchedConfigured =\n    orders.join(\n        payments,\n        (order, payment) -> new OrderPayment(order, payment),\n        JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5)),\n        StreamJoined.with(Serdes.String(), orderSerde, paymentSerde)\n            .withStoreName(\"order-payment-join\"));\n","Java","java",[324,817,818,848,872,906,925,929,933,946,957,965,1000,1024,1028,1032,1041,1051,1057,1085,1107,1138],{"__ignoreMap":575},[579,819,820,822,825,828,831,833,836,838,841,843,845],{"class":581,"line":582},[579,821,586],{"class":585},[579,823,824],{"class":637}," io",[579,826,827],{"class":641},".",[579,829,830],{"class":637},"stoatflow",[579,832,827],{"class":641},[579,834,835],{"class":637},"core",[579,837,827],{"class":641},[579,839,840],{"class":637},"topology",[579,842,827],{"class":641},[579,844,330],{"class":637},[579,846,847],{"class":641},";\n",[579,849,850,852,854,856,858,860,862,864,866,868,870],{"class":581,"line":593},[579,851,586],{"class":585},[579,853,824],{"class":637},[579,855,827],{"class":641},[579,857,830],{"class":637},[579,859,827],{"class":641},[579,861,835],{"class":637},[579,863,827],{"class":641},[579,865,840],{"class":637},[579,867,827],{"class":641},[579,869,336],{"class":637},[579,871,847],{"class":641},[579,873,874,876,879,881,884,886,889,891,894,896,899,901,904],{"class":581,"line":601},[579,875,586],{"class":585},[579,877,878],{"class":637}," org",[579,880,827],{"class":641},[579,882,883],{"class":637},"apache",[579,885,827],{"class":641},[579,887,888],{"class":637},"kafka",[579,890,827],{"class":641},[579,892,893],{"class":637},"common",[579,895,827],{"class":641},[579,897,898],{"class":637},"serialization",[579,900,827],{"class":641},[579,902,903],{"class":637},"Serdes",[579,905,847],{"class":641},[579,907,908,910,913,915,918,920,923],{"class":581,"line":609},[579,909,586],{"class":585},[579,911,912],{"class":637}," java",[579,914,827],{"class":641},[579,916,917],{"class":637},"time",[579,919,827],{"class":641},[579,921,922],{"class":637},"Duration",[579,924,847],{"class":641},[579,926,927],{"class":581,"line":617},[579,928,621],{"emptyLinePlaceholder":620},[579,930,931],{"class":581,"line":624},[579,932,628],{"class":627},[579,934,935,939,943],{"class":581,"line":631},[579,936,938],{"class":937},"si04Y","var",[579,940,942],{"class":941},"svycV"," matched",[579,944,945],{"class":641}," =\n",[579,947,948,951,953,955],{"class":581,"line":645},[579,949,950],{"class":941},"    orders",[579,952,827],{"class":641},[579,954,437],{"class":589},[579,956,653],{"class":641},[579,958,959,962],{"class":581,"line":656},[579,960,961],{"class":637},"        payments",[579,963,964],{"class":641},",\n",[579,966,967,970,973,976,979,982,985,988,990,992,994,996,998],{"class":581,"line":662},[579,968,969],{"class":641},"        (",[579,971,972],{"class":637},"order",[579,974,975],{"class":641},",",[579,977,978],{"class":637}," payment",[579,980,981],{"class":641},")",[579,983,984],{"class":641}," ->",[579,986,987],{"class":585}," new",[579,989,671],{"class":589},[579,991,692],{"class":641},[579,993,972],{"class":637},[579,995,975],{"class":641},[579,997,978],{"class":637},[579,999,804],{"class":641},[579,1001,1002,1005,1007,1009,1011,1013,1015,1017,1019,1021],{"class":581,"line":677},[579,1003,1004],{"class":941},"        JoinWindows",[579,1006,827],{"class":641},[579,1008,683],{"class":589},[579,1010,692],{"class":641},[579,1012,922],{"class":941},[579,1014,827],{"class":641},[579,1016,689],{"class":589},[579,1018,692],{"class":641},[579,1020,696],{"class":695},[579,1022,1023],{"class":641},")));\n",[579,1025,1026],{"class":581,"line":702},[579,1027,621],{"emptyLinePlaceholder":620},[579,1029,1030],{"class":581,"line":708},[579,1031,716],{"class":627},[579,1033,1034,1036,1039],{"class":581,"line":713},[579,1035,938],{"class":937},[579,1037,1038],{"class":941}," matchedConfigured",[579,1040,945],{"class":641},[579,1042,1043,1045,1047,1049],{"class":581,"line":719},[579,1044,950],{"class":941},[579,1046,827],{"class":641},[579,1048,437],{"class":589},[579,1050,653],{"class":641},[579,1052,1053,1055],{"class":581,"line":729},[579,1054,961],{"class":637},[579,1056,964],{"class":641},[579,1058,1059,1061,1063,1065,1067,1069,1071,1073,1075,1077,1079,1081,1083],{"class":581,"line":738},[579,1060,969],{"class":641},[579,1062,972],{"class":637},[579,1064,975],{"class":641},[579,1066,978],{"class":637},[579,1068,981],{"class":641},[579,1070,984],{"class":641},[579,1072,987],{"class":585},[579,1074,671],{"class":589},[579,1076,692],{"class":641},[579,1078,972],{"class":637},[579,1080,975],{"class":641},[579,1082,978],{"class":637},[579,1084,804],{"class":641},[579,1086,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105],{"class":581,"line":743},[579,1088,1004],{"class":941},[579,1090,827],{"class":641},[579,1092,683],{"class":589},[579,1094,692],{"class":641},[579,1096,922],{"class":941},[579,1098,827],{"class":641},[579,1100,689],{"class":589},[579,1102,692],{"class":641},[579,1104,696],{"class":695},[579,1106,699],{"class":641},[579,1108,1109,1112,1114,1116,1118,1120,1122,1124,1127,1130,1132,1135],{"class":581,"line":754},[579,1110,1111],{"class":941},"        StreamJoined",[579,1113,827],{"class":641},[579,1115,777],{"class":589},[579,1117,692],{"class":641},[579,1119,903],{"class":941},[579,1121,827],{"class":641},[579,1123,783],{"class":589},[579,1125,1126],{"class":641},"(),",[579,1128,1129],{"class":637}," orderSerde",[579,1131,975],{"class":641},[579,1133,1134],{"class":637}," paymentSerde",[579,1136,1137],{"class":641},")\n",[579,1139,1140,1142,1144,1146,1150,1153,1155],{"class":581,"line":771},[579,1141,792],{"class":641},[579,1143,795],{"class":589},[579,1145,692],{"class":641},[579,1147,1149],{"class":1148},"sSP4y","\"",[579,1151,1152],{"class":800},"order-payment-join",[579,1154,1149],{"class":1148},[579,1156,1157],{"class":641},"));\n",[320,1159,1160,327,1162,337,1164,1166],{},[324,1161,437],{},[324,1163,440],{},[324,1165,443],{}," are all available, each in value-only and key-aware forms:",[363,1168,1169,1176,1186],{},[366,1170,1171,1175],{},[355,1172,1173],{},[324,1174,437],{}," (inner) — emit only when both sides match within the window.",[366,1177,1178,1182,1183,1185],{},[355,1179,1180],{},[324,1181,440],{}," — emit for every left record; the right value is ",[324,1184,516],{}," when nothing matched by the time the window closes.",[366,1187,1188,1192,1193,1195],{},[355,1189,1190],{},[324,1191,443],{}," — emit for every record on either side; the unmatched side is ",[324,1194,516],{}," when the window closes.",[1197,1198,1200],"callout",{"color":1199,"icon":70},"info",[320,1201,1202,1203,427,1205,1207,1208,1211],{},"Unmatched ",[324,1204,440],{},[324,1206,443],{}," results are emitted when the ",[355,1209,1210],{},"window closes",", which is driven by the watermark — not on the bare arrival of the left record. A join window with no grace closes as soon as the watermark passes the window end.",[1213,1214,1216],"h3",{"id":1215},"configuring-the-join-with-streamjoined","Configuring the join with StreamJoined",[320,1218,1219,1221],{},[324,1220,336],{}," configures the two window stores that back a stream-stream join, plus naming and serdes:",[458,1223,1224,1234],{},[461,1225,1226],{},[464,1227,1228,1231],{},[467,1229,1230],{},"Method",[467,1232,1233],{},"Effect",[477,1235,1236,1246,1256,1273,1289,1299],{},[464,1237,1238,1243],{},[482,1239,1240],{},[324,1241,1242],{},"StreamJoined.as(name)",[482,1244,1245],{},"Name the join processor.",[464,1247,1248,1253],{},[482,1249,1250],{},[324,1251,1252],{},"StreamJoined.with(keySerde, leftValueSerde, rightValueSerde)",[482,1254,1255],{},"Set the serdes for both sides.",[464,1257,1258,1263],{},[482,1259,1260],{},[324,1261,1262],{},".withStoreName(base)",[482,1264,1265,1266,1269,1270,827],{},"Derive store names ",[324,1267,1268],{},"{base}-left-store"," and ",[324,1271,1272],{},"{base}-right-store",[464,1274,1275,1283],{},[482,1276,1277,427,1280],{},[324,1278,1279],{},".withThisStoreSupplier(...)",[324,1281,1282],{},".withOtherStoreSupplier(...)",[482,1284,1285,1286,1288],{},"Supply explicit window stores (\"this\" = the stream ",[324,1287,437],{}," is called on; \"other\" = the argument).",[464,1290,1291,1296],{},[482,1292,1293],{},[324,1294,1295],{},".withDslStoreSuppliers(StoreType.IN_MEMORY)",[482,1297,1298],{},"Choose in-memory or RocksDB for both stores (takes precedence over explicit suppliers).",[464,1300,1301,1309],{},[482,1302,1303,427,1306],{},[324,1304,1305],{},".withLoggingEnabled(...)",[324,1307,1308],{},".withLoggingDisabled()",[482,1310,1311],{},"Force changelog logging on\u002Foff for the join stores.",[320,1313,1314,1315,1318],{},"A key-aware joiner uses the same calls — pass a ",[324,1316,1317],{},"ValueJoinerWithKey"," instead:",[565,1320,1321,1437],{"group":567},[569,1322,1324],{"className":571,"code":1323,"filename":573,"language":574,"meta":575,"style":575},"import io.stoatflow.core.topology.ValueJoinerWithKey\n\nval labelled =\n    orders.join(\n        payments,\n        ValueJoinerWithKey\u003CString, Order, Payment, String> { customerId, order, payment ->\n            \"$customerId: ${order.total + payment.amount}\"\n        },\n        JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5)),\n    )\n",[324,1325,1326,1333,1337,1346,1354,1358,1389,1412,1417,1433],{"__ignoreMap":575},[579,1327,1328,1330],{"class":581,"line":582},[579,1329,586],{"class":585},[579,1331,1332],{"class":589}," io.stoatflow.core.topology.ValueJoinerWithKey\n",[579,1334,1335],{"class":581,"line":593},[579,1336,621],{"emptyLinePlaceholder":620},[579,1338,1339,1341,1344],{"class":581,"line":601},[579,1340,634],{"class":585},[579,1342,1343],{"class":637}," labelled ",[579,1345,642],{"class":641},[579,1347,1348,1350,1352],{"class":581,"line":609},[579,1349,648],{"class":637},[579,1351,437],{"class":589},[579,1353,653],{"class":637},[579,1355,1356],{"class":581,"line":617},[579,1357,659],{"class":637},[579,1359,1360,1363,1366,1369,1371,1374,1376,1379,1381,1383,1386],{"class":581,"line":624},[579,1361,1362],{"class":589},"        ValueJoinerWithKey",[579,1364,1365],{"class":637},"\u003C",[579,1367,783],{"class":1368},"sUxyF",[579,1370,327],{"class":637},[579,1372,1373],{"class":1368},"Order",[579,1375,327],{"class":637},[579,1377,1378],{"class":1368},"Payment",[579,1380,327],{"class":637},[579,1382,783],{"class":1368},[579,1384,1385],{"class":637},"> { customerId, order, payment ",[579,1387,1388],{"class":641},"->\n",[579,1390,1391,1394,1397,1400,1403,1406,1409],{"class":581,"line":631},[579,1392,1393],{"class":800},"            \"$customerId: ",[579,1395,1396],{"class":585},"${",[579,1398,1399],{"class":800},"order.total ",[579,1401,1402],{"class":937},"+",[579,1404,1405],{"class":800}," payment.amount",[579,1407,1408],{"class":585},"}",[579,1410,1411],{"class":800},"\"\n",[579,1413,1414],{"class":581,"line":645},[579,1415,1416],{"class":637},"        },\n",[579,1418,1419,1421,1423,1425,1427,1429,1431],{"class":581,"line":656},[579,1420,680],{"class":637},[579,1422,683],{"class":589},[579,1424,686],{"class":637},[579,1426,689],{"class":589},[579,1428,692],{"class":637},[579,1430,696],{"class":695},[579,1432,699],{"class":637},[579,1434,1435],{"class":581,"line":662},[579,1436,705],{"class":637},[569,1438,1440],{"className":812,"code":1439,"filename":814,"language":815,"meta":575,"style":575},"import io.stoatflow.core.topology.ValueJoinerWithKey;\n\nvar labelled =\n    orders.join(\n        payments,\n        (ValueJoinerWithKey\u003CString, Order, Payment, String>)\n            (customerId, order, payment) ->\n                customerId + \": \" + (order.total() + payment.amount()),\n        JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5)));\n",[324,1441,1442,1466,1470,1479,1489,1495,1525,1547,1590],{"__ignoreMap":575},[579,1443,1444,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464],{"class":581,"line":582},[579,1445,586],{"class":585},[579,1447,824],{"class":637},[579,1449,827],{"class":641},[579,1451,830],{"class":637},[579,1453,827],{"class":641},[579,1455,835],{"class":637},[579,1457,827],{"class":641},[579,1459,840],{"class":637},[579,1461,827],{"class":641},[579,1463,1317],{"class":637},[579,1465,847],{"class":641},[579,1467,1468],{"class":581,"line":593},[579,1469,621],{"emptyLinePlaceholder":620},[579,1471,1472,1474,1477],{"class":581,"line":601},[579,1473,938],{"class":937},[579,1475,1476],{"class":941}," labelled",[579,1478,945],{"class":641},[579,1480,1481,1483,1485,1487],{"class":581,"line":609},[579,1482,950],{"class":941},[579,1484,827],{"class":641},[579,1486,437],{"class":589},[579,1488,653],{"class":641},[579,1490,1491,1493],{"class":581,"line":617},[579,1492,961],{"class":637},[579,1494,964],{"class":641},[579,1496,1497,1499,1501,1503,1505,1507,1510,1512,1515,1517,1520,1523],{"class":581,"line":624},[579,1498,969],{"class":641},[579,1500,1317],{"class":637},[579,1502,1365],{"class":937},[579,1504,783],{"class":637},[579,1506,975],{"class":641},[579,1508,1509],{"class":637}," Order",[579,1511,975],{"class":641},[579,1513,1514],{"class":637}," Payment",[579,1516,975],{"class":641},[579,1518,1519],{"class":637}," String",[579,1521,1522],{"class":937},">",[579,1524,1137],{"class":641},[579,1526,1527,1530,1533,1535,1538,1540,1542,1544],{"class":581,"line":631},[579,1528,1529],{"class":641},"            (",[579,1531,1532],{"class":637},"customerId",[579,1534,975],{"class":641},[579,1536,1537],{"class":637}," order",[579,1539,975],{"class":641},[579,1541,978],{"class":637},[579,1543,981],{"class":641},[579,1545,1546],{"class":641}," ->\n",[579,1548,1549,1552,1554,1557,1560,1562,1565,1568,1570,1572,1575,1578,1580,1582,1584,1587],{"class":581,"line":645},[579,1550,1551],{"class":637},"                customerId ",[579,1553,1402],{"class":937},[579,1555,1556],{"class":1148}," \"",[579,1558,1559],{"class":800},": ",[579,1561,1149],{"class":1148},[579,1563,1564],{"class":937}," +",[579,1566,1567],{"class":641}," (",[579,1569,972],{"class":941},[579,1571,827],{"class":641},[579,1573,1574],{"class":589},"total",[579,1576,1577],{"class":641},"()",[579,1579,1564],{"class":937},[579,1581,978],{"class":941},[579,1583,827],{"class":641},[579,1585,1586],{"class":589},"amount",[579,1588,1589],{"class":641},"()),\n",[579,1591,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610],{"class":581,"line":656},[579,1593,1004],{"class":941},[579,1595,827],{"class":641},[579,1597,683],{"class":589},[579,1599,692],{"class":641},[579,1601,922],{"class":941},[579,1603,827],{"class":641},[579,1605,689],{"class":589},[579,1607,692],{"class":641},[579,1609,696],{"class":695},[579,1611,1023],{"class":641},[446,1613,1615],{"id":1614},"stream-table-joins","Stream-table joins",[320,1617,1618,1619,1622,1623,1626,1627,1630,1631,1634,1635,1638],{},"A stream-table join is an ",[355,1620,1621],{},"enrichment lookup",": each stream record is joined against the current value for its key in a ",[324,1624,1625],{},"KTable",". This is a point-in-time lookup — unlike a table-table join, ",[355,1628,1629],{},"updates to the table do not re-trigger the join",". Only stream records produce output. The table must be materialized (created via ",[324,1632,1633],{},"builder.table(...)"," or ",[324,1636,1637],{},"stream.toTable(...)"," with a store).",[320,1640,1641],{},"The stream key is preserved, so the join stays on the same lane — no re-keying.",[565,1643,1644,1749],{"group":567},[569,1645,1647],{"className":571,"code":1646,"filename":573,"language":574,"meta":575,"style":575},"import io.stoatflow.core.topology.Joined\n\n\u002F\u002F Inner: enrich each click with the user record, drop clicks with no user\nval enriched =\n    clicks.join(users) { click, user -> EnrichedClick(click, user) }\n\n\u002F\u002F Left: keep every click; user is null when the table has no entry for the key\nval enrichedAll =\n    clicks.leftJoin(\n        users,\n        Joined.`as`(\"click-user-join\"),\n    ) { click, user -> EnrichedClick(click, user) }\n",[324,1648,1649,1656,1660,1665,1674,1692,1696,1701,1710,1718,1723,1738],{"__ignoreMap":575},[579,1650,1651,1653],{"class":581,"line":582},[579,1652,586],{"class":585},[579,1654,1655],{"class":589}," io.stoatflow.core.topology.Joined\n",[579,1657,1658],{"class":581,"line":593},[579,1659,621],{"emptyLinePlaceholder":620},[579,1661,1662],{"class":581,"line":601},[579,1663,1664],{"class":627},"\u002F\u002F Inner: enrich each click with the user record, drop clicks with no user\n",[579,1666,1667,1669,1672],{"class":581,"line":609},[579,1668,634],{"class":585},[579,1670,1671],{"class":637}," enriched ",[579,1673,642],{"class":641},[579,1675,1676,1679,1681,1684,1686,1689],{"class":581,"line":617},[579,1677,1678],{"class":637},"    clicks.",[579,1680,437],{"class":589},[579,1682,1683],{"class":637},"(users) { click, user ",[579,1685,668],{"class":641},[579,1687,1688],{"class":589}," EnrichedClick",[579,1690,1691],{"class":637},"(click, user) }\n",[579,1693,1694],{"class":581,"line":624},[579,1695,621],{"emptyLinePlaceholder":620},[579,1697,1698],{"class":581,"line":631},[579,1699,1700],{"class":627},"\u002F\u002F Left: keep every click; user is null when the table has no entry for the key\n",[579,1702,1703,1705,1708],{"class":581,"line":645},[579,1704,634],{"class":585},[579,1706,1707],{"class":637}," enrichedAll ",[579,1709,642],{"class":641},[579,1711,1712,1714,1716],{"class":581,"line":656},[579,1713,1678],{"class":637},[579,1715,440],{"class":589},[579,1717,653],{"class":637},[579,1719,1720],{"class":581,"line":662},[579,1721,1722],{"class":637},"        users,\n",[579,1724,1725,1728,1731,1733,1736],{"class":581,"line":677},[579,1726,1727],{"class":637},"        Joined.",[579,1729,1730],{"class":589},"`as`",[579,1732,692],{"class":637},[579,1734,1735],{"class":800},"\"click-user-join\"",[579,1737,804],{"class":637},[579,1739,1740,1743,1745,1747],{"class":581,"line":702},[579,1741,1742],{"class":637},"    ) { click, user ",[579,1744,668],{"class":641},[579,1746,1688],{"class":589},[579,1748,1691],{"class":637},[569,1750,1752],{"className":812,"code":1751,"filename":814,"language":815,"meta":575,"style":575},"import io.stoatflow.core.topology.Joined;\n\n\u002F\u002F Inner: enrich each click with the user record, drop clicks with no user\nvar enriched =\n    clicks.join(users, (click, user) -> new EnrichedClick(click, user));\n\n\u002F\u002F Left: keep every click; user is null when the table has no entry for the key\nvar enrichedAll =\n    clicks.leftJoin(\n        users,\n        (click, user) -> new EnrichedClick(click, user),\n        Joined.as(\"click-user-join\"));\n",[324,1753,1754,1778,1782,1786,1795,1839,1843,1847,1856,1866,1873,1901],{"__ignoreMap":575},[579,1755,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776],{"class":581,"line":582},[579,1757,586],{"class":585},[579,1759,824],{"class":637},[579,1761,827],{"class":641},[579,1763,830],{"class":637},[579,1765,827],{"class":641},[579,1767,835],{"class":637},[579,1769,827],{"class":641},[579,1771,840],{"class":637},[579,1773,827],{"class":641},[579,1775,333],{"class":637},[579,1777,847],{"class":641},[579,1779,1780],{"class":581,"line":593},[579,1781,621],{"emptyLinePlaceholder":620},[579,1783,1784],{"class":581,"line":601},[579,1785,1664],{"class":627},[579,1787,1788,1790,1793],{"class":581,"line":609},[579,1789,938],{"class":937},[579,1791,1792],{"class":941}," enriched",[579,1794,945],{"class":641},[579,1796,1797,1800,1802,1804,1806,1809,1811,1813,1816,1818,1821,1823,1825,1827,1829,1831,1833,1835,1837],{"class":581,"line":617},[579,1798,1799],{"class":941},"    clicks",[579,1801,827],{"class":641},[579,1803,437],{"class":589},[579,1805,692],{"class":641},[579,1807,1808],{"class":637},"users",[579,1810,975],{"class":641},[579,1812,1567],{"class":641},[579,1814,1815],{"class":637},"click",[579,1817,975],{"class":641},[579,1819,1820],{"class":637}," user",[579,1822,981],{"class":641},[579,1824,984],{"class":641},[579,1826,987],{"class":585},[579,1828,1688],{"class":589},[579,1830,692],{"class":641},[579,1832,1815],{"class":637},[579,1834,975],{"class":641},[579,1836,1820],{"class":637},[579,1838,1157],{"class":641},[579,1840,1841],{"class":581,"line":624},[579,1842,621],{"emptyLinePlaceholder":620},[579,1844,1845],{"class":581,"line":631},[579,1846,1700],{"class":627},[579,1848,1849,1851,1854],{"class":581,"line":645},[579,1850,938],{"class":937},[579,1852,1853],{"class":941}," enrichedAll",[579,1855,945],{"class":641},[579,1857,1858,1860,1862,1864],{"class":581,"line":656},[579,1859,1799],{"class":941},[579,1861,827],{"class":641},[579,1863,440],{"class":589},[579,1865,653],{"class":641},[579,1867,1868,1871],{"class":581,"line":662},[579,1869,1870],{"class":637},"        users",[579,1872,964],{"class":641},[579,1874,1875,1877,1879,1881,1883,1885,1887,1889,1891,1893,1895,1897,1899],{"class":581,"line":677},[579,1876,969],{"class":641},[579,1878,1815],{"class":637},[579,1880,975],{"class":641},[579,1882,1820],{"class":637},[579,1884,981],{"class":641},[579,1886,984],{"class":641},[579,1888,987],{"class":585},[579,1890,1688],{"class":589},[579,1892,692],{"class":641},[579,1894,1815],{"class":637},[579,1896,975],{"class":641},[579,1898,1820],{"class":637},[579,1900,804],{"class":641},[579,1902,1903,1906,1908,1911,1913,1915,1918,1920],{"class":581,"line":702},[579,1904,1905],{"class":941},"        Joined",[579,1907,827],{"class":641},[579,1909,1910],{"class":589},"as",[579,1912,692],{"class":641},[579,1914,1149],{"class":1148},[579,1916,1917],{"class":800},"click-user-join",[579,1919,1149],{"class":1148},[579,1921,1157],{"class":641},[320,1923,1924,1927,1928,1932,1933,1935,1936,427,1939,427,1942,1945,1946,1949,1950,1953,1954,1957,1958,1961,1962,1965,1966,1968,1969,1269,1971,1973,1974,1976],{},[324,1925,1926],{},"Joined.as(name)"," names the join processor — that's its only ",[1929,1930,1931],"em",{},"effective"," setting. For Kafka Streams source compatibility ",[324,1934,333],{}," also accepts serdes (",[324,1937,1938],{},"withKeySerde",[324,1940,1941],{},"withValueSerde",[324,1943,1944],{},"withOtherValueSerde",") and ",[324,1947,1948],{},"withGracePeriod(...)",", but these are ",[355,1951,1952],{},"advisory"," in StoatFlow: they're stored so KS code compiles, while serde resolution actually comes from ",[324,1955,1956],{},"Consumed","\u002F",[324,1959,1960],{},"Grouped"," upstream (a documented divergence — see the ",[343,1963,1964],{"href":302},"compatibility matrix","). Key-aware joiners (",[324,1967,1317],{},") are available for both ",[324,1970,437],{},[324,1972,440],{},". There is no ",[324,1975,443],{}," for stream-table joins — an outer join has no stream record to anchor an unmatched table entry to.",[1197,1978,1980],{"color":1199,"icon":1979},"i-lucide-info",[320,1981,1982,1985,1986,1989,1990,1992,1993,1996],{},[355,1983,1984],{},"Worked example."," The ",[324,1987,1988],{},"news-feed-subscription-processor"," example enriches a windowed result stream with subscriber and user detail held in a ",[324,1991,1625],{},", using exactly this shape — ",[324,1994,1995],{},"subscriptionResults.join(subscriptionAndUserTable, joiner, Joined.as(\"...\"))"," — to build the final notification before writing to the sink.",[446,1998,2000],{"id":1999},"table-table-joins","Table-table joins",[320,2002,2003,2004,2007,2008,2011,2012,2014],{},"A table-table join matches the two tables ",[355,2005,2006],{},"by key",": for each key present in both, it emits the joined value, and it re-emits whenever ",[355,2009,2010],{},"either"," side updates. The result is itself a ",[324,2013,1625],{},", so it carries changelog (tombstone) semantics — a deletion on the relevant side deletes the join result.",[320,2016,2017],{},"Both tables must be materialized. The three join types follow KS semantics exactly:",[458,2019,2020,2032],{},[461,2021,2022],{},[464,2023,2024,2026,2029],{},[467,2025,1230],{},[467,2027,2028],{},"Emits",[467,2030,2031],{},"Result deleted when",[477,2033,2034,2047,2061],{},[464,2035,2036,2041,2044],{},[482,2037,2038,2040],{},[324,2039,437],{}," (inner)",[482,2042,2043],{},"only when both keys are present",[482,2045,2046],{},"either side becomes a tombstone",[464,2048,2049,2053,2058],{},[482,2050,2051],{},[324,2052,440],{},[482,2054,2055,2056],{},"for every left update; right may be ",[324,2057,516],{},[482,2059,2060],{},"the left side becomes a tombstone",[464,2062,2063,2067,2072],{},[482,2064,2065],{},[324,2066,443],{},[482,2068,2069,2070],{},"for every update to either side; either may be ",[324,2071,516],{},[482,2073,2074],{},"both sides become tombstones",[565,2076,2077,2229],{"group":567},[569,2078,2080],{"className":571,"code":2079,"filename":573,"language":574,"meta":575,"style":575},"import io.stoatflow.core.state.StateStore\nimport io.stoatflow.core.topology.Materialized\nimport io.stoatflow.core.topology.Named\n\n\u002F\u002F Inner join on equal keys\nval joined =\n    accounts.join(balances) { account, balance -> AccountBalance(account, balance) }\n\n\u002F\u002F Outer join, named and materialized into its own store\nval outer =\n    accounts.outerJoin(\n        balances,\n        { account, balance -> AccountBalance(account, balance) },\n        Named.`as`(\"account-balance-join\"),\n        Materialized.`as`\u003CString, AccountBalance, StateStore>(\"account-balance\"),\n    )\n",[324,2081,2082,2089,2096,2103,2107,2112,2121,2139,2143,2148,2157,2165,2170,2182,2196,2225],{"__ignoreMap":575},[579,2083,2084,2086],{"class":581,"line":582},[579,2085,586],{"class":585},[579,2087,2088],{"class":589}," io.stoatflow.core.state.StateStore\n",[579,2090,2091,2093],{"class":581,"line":593},[579,2092,586],{"class":585},[579,2094,2095],{"class":589}," io.stoatflow.core.topology.Materialized\n",[579,2097,2098,2100],{"class":581,"line":601},[579,2099,586],{"class":585},[579,2101,2102],{"class":589}," io.stoatflow.core.topology.Named\n",[579,2104,2105],{"class":581,"line":609},[579,2106,621],{"emptyLinePlaceholder":620},[579,2108,2109],{"class":581,"line":617},[579,2110,2111],{"class":627},"\u002F\u002F Inner join on equal keys\n",[579,2113,2114,2116,2119],{"class":581,"line":624},[579,2115,634],{"class":585},[579,2117,2118],{"class":637}," joined ",[579,2120,642],{"class":641},[579,2122,2123,2126,2128,2131,2133,2136],{"class":581,"line":631},[579,2124,2125],{"class":637},"    accounts.",[579,2127,437],{"class":589},[579,2129,2130],{"class":637},"(balances) { account, balance ",[579,2132,668],{"class":641},[579,2134,2135],{"class":589}," AccountBalance",[579,2137,2138],{"class":637},"(account, balance) }\n",[579,2140,2141],{"class":581,"line":645},[579,2142,621],{"emptyLinePlaceholder":620},[579,2144,2145],{"class":581,"line":656},[579,2146,2147],{"class":627},"\u002F\u002F Outer join, named and materialized into its own store\n",[579,2149,2150,2152,2155],{"class":581,"line":662},[579,2151,634],{"class":585},[579,2153,2154],{"class":637}," outer ",[579,2156,642],{"class":641},[579,2158,2159,2161,2163],{"class":581,"line":677},[579,2160,2125],{"class":637},[579,2162,443],{"class":589},[579,2164,653],{"class":637},[579,2166,2167],{"class":581,"line":702},[579,2168,2169],{"class":637},"        balances,\n",[579,2171,2172,2175,2177,2179],{"class":581,"line":708},[579,2173,2174],{"class":637},"        { account, balance ",[579,2176,668],{"class":641},[579,2178,2135],{"class":589},[579,2180,2181],{"class":637},"(account, balance) },\n",[579,2183,2184,2187,2189,2191,2194],{"class":581,"line":713},[579,2185,2186],{"class":637},"        Named.",[579,2188,1730],{"class":589},[579,2190,692],{"class":637},[579,2192,2193],{"class":800},"\"account-balance-join\"",[579,2195,804],{"class":637},[579,2197,2198,2201,2203,2205,2207,2209,2212,2214,2217,2220,2223],{"class":581,"line":719},[579,2199,2200],{"class":637},"        Materialized.",[579,2202,1730],{"class":589},[579,2204,1365],{"class":637},[579,2206,783],{"class":1368},[579,2208,327],{"class":637},[579,2210,2211],{"class":1368},"AccountBalance",[579,2213,327],{"class":637},[579,2215,2216],{"class":1368},"StateStore",[579,2218,2219],{"class":637},">(",[579,2221,2222],{"class":800},"\"account-balance\"",[579,2224,804],{"class":637},[579,2226,2227],{"class":581,"line":729},[579,2228,705],{"class":637},[569,2230,2232],{"className":812,"code":2231,"filename":814,"language":815,"meta":575,"style":575},"import io.stoatflow.core.state.StateStore;\nimport io.stoatflow.core.topology.Materialized;\nimport io.stoatflow.core.topology.Named;\n\n\u002F\u002F Inner join on equal keys\nvar joined =\n    accounts.join(balances, (account, balance) -> new AccountBalance(account, balance));\n\n\u002F\u002F Outer join, named and materialized into its own store\nvar outer =\n    accounts.outerJoin(\n        balances,\n        (account, balance) -> new AccountBalance(account, balance),\n        Named.as(\"account-balance-join\"),\n        Materialized.\u003CString, AccountBalance, StateStore>as(\"account-balance\"));\n",[324,2233,2234,2259,2284,2309,2313,2317,2326,2370,2374,2378,2387,2397,2404,2432,2452],{"__ignoreMap":575},[579,2235,2236,2238,2240,2242,2244,2246,2248,2250,2253,2255,2257],{"class":581,"line":582},[579,2237,586],{"class":585},[579,2239,824],{"class":637},[579,2241,827],{"class":641},[579,2243,830],{"class":637},[579,2245,827],{"class":641},[579,2247,835],{"class":637},[579,2249,827],{"class":641},[579,2251,2252],{"class":637},"state",[579,2254,827],{"class":641},[579,2256,2216],{"class":637},[579,2258,847],{"class":641},[579,2260,2261,2263,2265,2267,2269,2271,2273,2275,2277,2279,2282],{"class":581,"line":593},[579,2262,586],{"class":585},[579,2264,824],{"class":637},[579,2266,827],{"class":641},[579,2268,830],{"class":637},[579,2270,827],{"class":641},[579,2272,835],{"class":637},[579,2274,827],{"class":641},[579,2276,840],{"class":637},[579,2278,827],{"class":641},[579,2280,2281],{"class":637},"Materialized",[579,2283,847],{"class":641},[579,2285,2286,2288,2290,2292,2294,2296,2298,2300,2302,2304,2307],{"class":581,"line":601},[579,2287,586],{"class":585},[579,2289,824],{"class":637},[579,2291,827],{"class":641},[579,2293,830],{"class":637},[579,2295,827],{"class":641},[579,2297,835],{"class":637},[579,2299,827],{"class":641},[579,2301,840],{"class":637},[579,2303,827],{"class":641},[579,2305,2306],{"class":637},"Named",[579,2308,847],{"class":641},[579,2310,2311],{"class":581,"line":609},[579,2312,621],{"emptyLinePlaceholder":620},[579,2314,2315],{"class":581,"line":617},[579,2316,2111],{"class":627},[579,2318,2319,2321,2324],{"class":581,"line":624},[579,2320,938],{"class":937},[579,2322,2323],{"class":941}," joined",[579,2325,945],{"class":641},[579,2327,2328,2331,2333,2335,2337,2340,2342,2344,2347,2349,2352,2354,2356,2358,2360,2362,2364,2366,2368],{"class":581,"line":631},[579,2329,2330],{"class":941},"    accounts",[579,2332,827],{"class":641},[579,2334,437],{"class":589},[579,2336,692],{"class":641},[579,2338,2339],{"class":637},"balances",[579,2341,975],{"class":641},[579,2343,1567],{"class":641},[579,2345,2346],{"class":637},"account",[579,2348,975],{"class":641},[579,2350,2351],{"class":637}," balance",[579,2353,981],{"class":641},[579,2355,984],{"class":641},[579,2357,987],{"class":585},[579,2359,2135],{"class":589},[579,2361,692],{"class":641},[579,2363,2346],{"class":637},[579,2365,975],{"class":641},[579,2367,2351],{"class":637},[579,2369,1157],{"class":641},[579,2371,2372],{"class":581,"line":645},[579,2373,621],{"emptyLinePlaceholder":620},[579,2375,2376],{"class":581,"line":656},[579,2377,2147],{"class":627},[579,2379,2380,2382,2385],{"class":581,"line":662},[579,2381,938],{"class":937},[579,2383,2384],{"class":941}," outer",[579,2386,945],{"class":641},[579,2388,2389,2391,2393,2395],{"class":581,"line":677},[579,2390,2330],{"class":941},[579,2392,827],{"class":641},[579,2394,443],{"class":589},[579,2396,653],{"class":641},[579,2398,2399,2402],{"class":581,"line":702},[579,2400,2401],{"class":637},"        balances",[579,2403,964],{"class":641},[579,2405,2406,2408,2410,2412,2414,2416,2418,2420,2422,2424,2426,2428,2430],{"class":581,"line":708},[579,2407,969],{"class":641},[579,2409,2346],{"class":637},[579,2411,975],{"class":641},[579,2413,2351],{"class":637},[579,2415,981],{"class":641},[579,2417,984],{"class":641},[579,2419,987],{"class":585},[579,2421,2135],{"class":589},[579,2423,692],{"class":641},[579,2425,2346],{"class":637},[579,2427,975],{"class":641},[579,2429,2351],{"class":637},[579,2431,804],{"class":641},[579,2433,2434,2437,2439,2441,2443,2445,2448,2450],{"class":581,"line":713},[579,2435,2436],{"class":941},"        Named",[579,2438,827],{"class":641},[579,2440,1910],{"class":589},[579,2442,692],{"class":641},[579,2444,1149],{"class":1148},[579,2446,2447],{"class":800},"account-balance-join",[579,2449,1149],{"class":1148},[579,2451,804],{"class":641},[579,2453,2454,2457,2459,2461,2463,2465,2467,2469,2472,2474,2476,2478,2480,2483,2485],{"class":581,"line":719},[579,2455,2456],{"class":637},"        Materialized",[579,2458,827],{"class":641},[579,2460,1365],{"class":937},[579,2462,783],{"class":637},[579,2464,975],{"class":641},[579,2466,2135],{"class":637},[579,2468,975],{"class":641},[579,2470,2471],{"class":637}," StateStore",[579,2473,1522],{"class":937},[579,2475,1910],{"class":589},[579,2477,692],{"class":641},[579,2479,1149],{"class":1148},[579,2481,2482],{"class":800},"account-balance",[579,2484,1149],{"class":1148},[579,2486,1157],{"class":641},[320,2488,2489,2490,427,2492,427,2494,2496,2497,2499,2500,2502],{},"Each of ",[324,2491,437],{},[324,2493,440],{},[324,2495,443],{}," has overloads taking an optional ",[324,2498,2306],{}," and an optional ",[324,2501,2281],{}," — name the node, materialize the result, or both.",[446,2504,2506],{"id":2505},"foreign-key-joins","Foreign-key joins",[320,2508,2509,2510,2513,2514,2517,2518,2520],{},"A foreign-key (FK) join joins two tables on a key ",[355,2511,2512],{},"extracted from the left table's value"," rather than on the record key. This is the table-table equivalent of a relational ",[324,2515,2516],{},"JOIN ... ON left.fk = right.id",". The right table is keyed by the foreign key; the result keeps the ",[355,2519,430],{}," table's key.",[320,2522,2523,2524,2527],{},"The signature is ",[324,2525,2526],{},"left.join(right, foreignKeyExtractor, joiner, tableJoined, ...)",":",[363,2529,2530,2549,2556],{},[366,2531,2532,2537,2538,2541,2542,2545,2546,2548],{},[355,2533,2534],{},[324,2535,2536],{},"foreignKeyExtractor"," derives the right table's key from the left value — ",[324,2539,2540],{},"(V) -> KO?"," (value only) or ",[324,2543,2544],{},"(K, V) -> KO?"," (key + value). A ",[324,2547,516],{}," foreign key means no join is possible and the record is ignored.",[366,2550,2551,2555],{},[355,2552,2553],{},[324,2554,455],{}," combines the left value with the matched right value.",[366,2557,2558,2563],{},[355,2559,2560],{},[324,2561,2562],{},"TableJoined.as(name)"," names the join processors (its only setting).",[320,2565,2566,2567,1567,2569,1945,2571,1567,2573,2575,2576,2579],{},"Both ",[355,2568,426],{},[324,2570,437],{},[355,2572,430],{},[324,2574,440],{},") FK joins are supported; ",[355,2577,2578],{},"outer is not"," — there is no left key to anchor an unmatched right row to. The FK join re-emits when the left value changes (re-extract the FK, look up the right table) and when the right value changes (re-evaluate every left row that points at it).",[565,2581,2582,2734],{"group":567},[569,2583,2585],{"className":571,"code":2584,"filename":573,"language":574,"meta":575,"style":575},"import io.stoatflow.core.state.StateStore\nimport io.stoatflow.core.topology.Materialized\nimport io.stoatflow.core.topology.TableJoined\n\n\u002F\u002F orders keyed by orderId, customers keyed by customerId.\n\u002F\u002F Extract customerId from each order, look it up in customers.\nval enrichedOrders =\n    orders.join(\n        customers,\n        { order -> order.customerId },           \u002F\u002F foreign-key extractor (left value -> right key)\n        { order, customer -> EnrichedOrder(order, customer) },\n        TableJoined.`as`(\"order-customer-join\"),\n        Materialized.`as`\u003CString, EnrichedOrder, StateStore>(\"enriched-orders\")\n            .withKeySerde(Serdes.String())\n            .withValueSerde(enrichedOrderSerde),\n    )\n",[324,2586,2587,2593,2599,2606,2610,2615,2620,2629,2637,2642,2655,2668,2682,2708,2721,2730],{"__ignoreMap":575},[579,2588,2589,2591],{"class":581,"line":582},[579,2590,586],{"class":585},[579,2592,2088],{"class":589},[579,2594,2595,2597],{"class":581,"line":593},[579,2596,586],{"class":585},[579,2598,2095],{"class":589},[579,2600,2601,2603],{"class":581,"line":601},[579,2602,586],{"class":585},[579,2604,2605],{"class":589}," io.stoatflow.core.topology.TableJoined\n",[579,2607,2608],{"class":581,"line":609},[579,2609,621],{"emptyLinePlaceholder":620},[579,2611,2612],{"class":581,"line":617},[579,2613,2614],{"class":627},"\u002F\u002F orders keyed by orderId, customers keyed by customerId.\n",[579,2616,2617],{"class":581,"line":624},[579,2618,2619],{"class":627},"\u002F\u002F Extract customerId from each order, look it up in customers.\n",[579,2621,2622,2624,2627],{"class":581,"line":631},[579,2623,634],{"class":585},[579,2625,2626],{"class":637}," enrichedOrders ",[579,2628,642],{"class":641},[579,2630,2631,2633,2635],{"class":581,"line":645},[579,2632,648],{"class":637},[579,2634,437],{"class":589},[579,2636,653],{"class":637},[579,2638,2639],{"class":581,"line":656},[579,2640,2641],{"class":637},"        customers,\n",[579,2643,2644,2647,2649,2652],{"class":581,"line":662},[579,2645,2646],{"class":637},"        { order ",[579,2648,668],{"class":641},[579,2650,2651],{"class":637}," order.customerId },           ",[579,2653,2654],{"class":627},"\u002F\u002F foreign-key extractor (left value -> right key)\n",[579,2656,2657,2660,2662,2665],{"class":581,"line":677},[579,2658,2659],{"class":637},"        { order, customer ",[579,2661,668],{"class":641},[579,2663,2664],{"class":589}," EnrichedOrder",[579,2666,2667],{"class":637},"(order, customer) },\n",[579,2669,2670,2673,2675,2677,2680],{"class":581,"line":702},[579,2671,2672],{"class":637},"        TableJoined.",[579,2674,1730],{"class":589},[579,2676,692],{"class":637},[579,2678,2679],{"class":800},"\"order-customer-join\"",[579,2681,804],{"class":637},[579,2683,2684,2686,2688,2690,2692,2694,2697,2699,2701,2703,2706],{"class":581,"line":708},[579,2685,2200],{"class":637},[579,2687,1730],{"class":589},[579,2689,1365],{"class":637},[579,2691,783],{"class":1368},[579,2693,327],{"class":637},[579,2695,2696],{"class":1368},"EnrichedOrder",[579,2698,327],{"class":637},[579,2700,2216],{"class":1368},[579,2702,2219],{"class":637},[579,2704,2705],{"class":800},"\"enriched-orders\"",[579,2707,1137],{"class":637},[579,2709,2710,2712,2714,2716,2718],{"class":581,"line":713},[579,2711,792],{"class":637},[579,2713,1938],{"class":589},[579,2715,780],{"class":637},[579,2717,783],{"class":589},[579,2719,2720],{"class":637},"())\n",[579,2722,2723,2725,2727],{"class":581,"line":719},[579,2724,792],{"class":637},[579,2726,1941],{"class":589},[579,2728,2729],{"class":637},"(enrichedOrderSerde),\n",[579,2731,2732],{"class":581,"line":729},[579,2733,705],{"class":637},[569,2735,2737],{"className":812,"code":2736,"filename":814,"language":815,"meta":575,"style":575},"import io.stoatflow.core.state.StateStore;\nimport io.stoatflow.core.topology.Materialized;\nimport io.stoatflow.core.topology.TableJoined;\n\n\u002F\u002F orders keyed by orderId, customers keyed by customerId.\n\u002F\u002F Extract customerId from each order, look it up in customers.\nvar enrichedOrders =\n    orders.join(\n        customers,\n        order -> order.customerId(),             \u002F\u002F foreign-key extractor (left value -> right key)\n        (order, customer) -> new EnrichedOrder(order, customer),\n        TableJoined.as(\"order-customer-join\"),\n        Materialized.\u003CString, EnrichedOrder, StateStore>as(\"enriched-orders\")\n            .withKeySerde(Serdes.String())\n            .withValueSerde(enrichedOrderSerde));\n",[324,2738,2739,2763,2787,2811,2815,2819,2823,2832,2842,2849,2867,2896,2916,2949,2965],{"__ignoreMap":575},[579,2740,2741,2743,2745,2747,2749,2751,2753,2755,2757,2759,2761],{"class":581,"line":582},[579,2742,586],{"class":585},[579,2744,824],{"class":637},[579,2746,827],{"class":641},[579,2748,830],{"class":637},[579,2750,827],{"class":641},[579,2752,835],{"class":637},[579,2754,827],{"class":641},[579,2756,2252],{"class":637},[579,2758,827],{"class":641},[579,2760,2216],{"class":637},[579,2762,847],{"class":641},[579,2764,2765,2767,2769,2771,2773,2775,2777,2779,2781,2783,2785],{"class":581,"line":593},[579,2766,586],{"class":585},[579,2768,824],{"class":637},[579,2770,827],{"class":641},[579,2772,830],{"class":637},[579,2774,827],{"class":641},[579,2776,835],{"class":637},[579,2778,827],{"class":641},[579,2780,840],{"class":637},[579,2782,827],{"class":641},[579,2784,2281],{"class":637},[579,2786,847],{"class":641},[579,2788,2789,2791,2793,2795,2797,2799,2801,2803,2805,2807,2809],{"class":581,"line":601},[579,2790,586],{"class":585},[579,2792,824],{"class":637},[579,2794,827],{"class":641},[579,2796,830],{"class":637},[579,2798,827],{"class":641},[579,2800,835],{"class":637},[579,2802,827],{"class":641},[579,2804,840],{"class":637},[579,2806,827],{"class":641},[579,2808,340],{"class":637},[579,2810,847],{"class":641},[579,2812,2813],{"class":581,"line":609},[579,2814,621],{"emptyLinePlaceholder":620},[579,2816,2817],{"class":581,"line":617},[579,2818,2614],{"class":627},[579,2820,2821],{"class":581,"line":624},[579,2822,2619],{"class":627},[579,2824,2825,2827,2830],{"class":581,"line":631},[579,2826,938],{"class":937},[579,2828,2829],{"class":941}," enrichedOrders",[579,2831,945],{"class":641},[579,2833,2834,2836,2838,2840],{"class":581,"line":645},[579,2835,950],{"class":941},[579,2837,827],{"class":641},[579,2839,437],{"class":589},[579,2841,653],{"class":641},[579,2843,2844,2847],{"class":581,"line":656},[579,2845,2846],{"class":637},"        customers",[579,2848,964],{"class":641},[579,2850,2851,2854,2856,2858,2860,2862,2864],{"class":581,"line":662},[579,2852,2853],{"class":637},"        order ",[579,2855,668],{"class":641},[579,2857,1537],{"class":941},[579,2859,827],{"class":641},[579,2861,1532],{"class":589},[579,2863,1126],{"class":641},[579,2865,2866],{"class":627},"             \u002F\u002F foreign-key extractor (left value -> right key)\n",[579,2868,2869,2871,2873,2875,2878,2880,2882,2884,2886,2888,2890,2892,2894],{"class":581,"line":677},[579,2870,969],{"class":641},[579,2872,972],{"class":637},[579,2874,975],{"class":641},[579,2876,2877],{"class":637}," customer",[579,2879,981],{"class":641},[579,2881,984],{"class":641},[579,2883,987],{"class":585},[579,2885,2664],{"class":589},[579,2887,692],{"class":641},[579,2889,972],{"class":637},[579,2891,975],{"class":641},[579,2893,2877],{"class":637},[579,2895,804],{"class":641},[579,2897,2898,2901,2903,2905,2907,2909,2912,2914],{"class":581,"line":702},[579,2899,2900],{"class":941},"        TableJoined",[579,2902,827],{"class":641},[579,2904,1910],{"class":589},[579,2906,692],{"class":641},[579,2908,1149],{"class":1148},[579,2910,2911],{"class":800},"order-customer-join",[579,2913,1149],{"class":1148},[579,2915,804],{"class":641},[579,2917,2918,2920,2922,2924,2926,2928,2930,2932,2934,2936,2938,2940,2942,2945,2947],{"class":581,"line":708},[579,2919,2456],{"class":637},[579,2921,827],{"class":641},[579,2923,1365],{"class":937},[579,2925,783],{"class":637},[579,2927,975],{"class":641},[579,2929,2664],{"class":637},[579,2931,975],{"class":641},[579,2933,2471],{"class":637},[579,2935,1522],{"class":937},[579,2937,1910],{"class":589},[579,2939,692],{"class":641},[579,2941,1149],{"class":1148},[579,2943,2944],{"class":800},"enriched-orders",[579,2946,1149],{"class":1148},[579,2948,1137],{"class":641},[579,2950,2951,2953,2955,2957,2959,2961,2963],{"class":581,"line":713},[579,2952,792],{"class":641},[579,2954,1938],{"class":589},[579,2956,692],{"class":641},[579,2958,903],{"class":941},[579,2960,827],{"class":641},[579,2962,783],{"class":589},[579,2964,2720],{"class":641},[579,2966,2967,2969,2971,2973,2976],{"class":581,"line":719},[579,2968,792],{"class":641},[579,2970,1941],{"class":589},[579,2972,692],{"class":641},[579,2974,2975],{"class":637},"enrichedOrderSerde",[579,2977,1157],{"class":641},[320,2979,2980,2981,2984,2985,2988,2989,2992,2993,2996,2997,3000],{},"The Java foreign-key overloads take ",[324,2982,2983],{},"java.util.function.Function\u003CV, KO>"," (or ",[324,2986,2987],{},"BiFunction\u003CK, V, KO>"," for the key-aware form) for the extractor — the same shape as the current Kafka Streams FK-join API — and ",[324,2990,2991],{},"ValueJoiner\u003CV, VO, VR>"," for the joiner, so a method reference like ",[324,2994,2995],{},"Order::customerId"," works as the extractor and a constructor reference like ",[324,2998,2999],{},"EnrichedOrder::new"," works as the joiner.",[446,3002,3004],{"id":3003},"co-grouping","Co-grouping",[320,3006,3007,3008,3011],{},"Co-grouping aggregates ",[355,3009,3010],{},"multiple grouped streams of different value types"," into one result, each stream contributing through its own aggregator. It's the join-like answer to \"fold orders and payments for the same customer into one summary\" — without windowing the two sides against each other.",[320,3013,3014,3015,3018,3019,3022,3023,2527],{},"Start a cogroup from a ",[324,3016,3017],{},"KGroupedStream",", chain ",[324,3020,3021],{},".cogroup(other) { ... }"," for each additional stream, then ",[324,3024,3025],{},".aggregate(initializer)",[565,3027,3028,3196],{"group":567},[569,3029,3031],{"className":571,"code":3030,"filename":573,"language":574,"meta":575,"style":575},"import io.stoatflow.core.topology.Materialized\n\nval orders = orderStream.groupByKey()       \u002F\u002F KGroupedStream\u003CString, Order>\nval payments = paymentStream.groupByKey()   \u002F\u002F KGroupedStream\u003CString, Payment>\n\nval summary: KTable\u003CString, CustomerSummary> =\n    orders\n        .cogroup { _, order, agg -> agg.addOrder(order) }\n        .cogroup(payments) { _, payment, agg -> agg.addPayment(payment) }\n        .aggregate(\n            { CustomerSummary() },\n            Materialized.`as`(\"customer-summary\"),\n        )\n",[324,3032,3033,3039,3043,3065,3085,3089,3112,3117,3139,3158,3167,3177,3191],{"__ignoreMap":575},[579,3034,3035,3037],{"class":581,"line":582},[579,3036,586],{"class":585},[579,3038,2095],{"class":589},[579,3040,3041],{"class":581,"line":593},[579,3042,621],{"emptyLinePlaceholder":620},[579,3044,3045,3047,3050,3053,3056,3059,3062],{"class":581,"line":601},[579,3046,634],{"class":585},[579,3048,3049],{"class":637}," orders ",[579,3051,3052],{"class":641},"=",[579,3054,3055],{"class":637}," orderStream.",[579,3057,3058],{"class":589},"groupByKey",[579,3060,3061],{"class":637},"()       ",[579,3063,3064],{"class":627},"\u002F\u002F KGroupedStream\u003CString, Order>\n",[579,3066,3067,3069,3072,3074,3077,3079,3082],{"class":581,"line":609},[579,3068,634],{"class":585},[579,3070,3071],{"class":637}," payments ",[579,3073,3052],{"class":641},[579,3075,3076],{"class":637}," paymentStream.",[579,3078,3058],{"class":589},[579,3080,3081],{"class":637},"()   ",[579,3083,3084],{"class":627},"\u002F\u002F KGroupedStream\u003CString, Payment>\n",[579,3086,3087],{"class":581,"line":617},[579,3088,621],{"emptyLinePlaceholder":620},[579,3090,3091,3093,3096,3098,3100,3102,3104,3107,3110],{"class":581,"line":624},[579,3092,634],{"class":585},[579,3094,3095],{"class":637}," summary: ",[579,3097,1625],{"class":1368},[579,3099,1365],{"class":637},[579,3101,783],{"class":1368},[579,3103,327],{"class":637},[579,3105,3106],{"class":1368},"CustomerSummary",[579,3108,3109],{"class":637},"> ",[579,3111,642],{"class":641},[579,3113,3114],{"class":581,"line":631},[579,3115,3116],{"class":637},"    orders\n",[579,3118,3119,3122,3125,3128,3130,3133,3136],{"class":581,"line":645},[579,3120,3121],{"class":637},"        .",[579,3123,3124],{"class":589},"cogroup",[579,3126,3127],{"class":637}," { _, order, agg ",[579,3129,668],{"class":641},[579,3131,3132],{"class":637}," agg.",[579,3134,3135],{"class":589},"addOrder",[579,3137,3138],{"class":637},"(order) }\n",[579,3140,3141,3143,3145,3148,3150,3152,3155],{"class":581,"line":656},[579,3142,3121],{"class":637},[579,3144,3124],{"class":589},[579,3146,3147],{"class":637},"(payments) { _, payment, agg ",[579,3149,668],{"class":641},[579,3151,3132],{"class":637},[579,3153,3154],{"class":589},"addPayment",[579,3156,3157],{"class":637},"(payment) }\n",[579,3159,3160,3162,3165],{"class":581,"line":662},[579,3161,3121],{"class":637},[579,3163,3164],{"class":589},"aggregate",[579,3166,653],{"class":637},[579,3168,3169,3172,3174],{"class":581,"line":677},[579,3170,3171],{"class":637},"            { ",[579,3173,3106],{"class":589},[579,3175,3176],{"class":637},"() },\n",[579,3178,3179,3182,3184,3186,3189],{"class":581,"line":702},[579,3180,3181],{"class":637},"            Materialized.",[579,3183,1730],{"class":589},[579,3185,692],{"class":637},[579,3187,3188],{"class":800},"\"customer-summary\"",[579,3190,804],{"class":637},[579,3192,3193],{"class":581,"line":708},[579,3194,3195],{"class":637},"        )\n",[569,3197,3199],{"className":812,"code":3198,"filename":814,"language":815,"meta":575,"style":575},"import io.stoatflow.core.topology.Materialized;\n\nvar orders = orderStream.groupByKey();       \u002F\u002F KGroupedStream\u003CString, Order>\nvar payments = paymentStream.groupByKey();   \u002F\u002F KGroupedStream\u003CString, Payment>\n\nKTable\u003CString, CustomerSummary> summary =\n    orders\n        .cogroup((key, order, agg) -> agg.addOrder(order))\n        .cogroup(payments, (key, payment, agg) -> agg.addPayment(payment))\n        .aggregate(\n            CustomerSummary::new,\n            Materialized.as(\"customer-summary\"));\n",[324,3200,3201,3225,3229,3252,3273,3277,3297,3301,3339,3381,3389,3399],{"__ignoreMap":575},[579,3202,3203,3205,3207,3209,3211,3213,3215,3217,3219,3221,3223],{"class":581,"line":582},[579,3204,586],{"class":585},[579,3206,824],{"class":637},[579,3208,827],{"class":641},[579,3210,830],{"class":637},[579,3212,827],{"class":641},[579,3214,835],{"class":637},[579,3216,827],{"class":641},[579,3218,840],{"class":637},[579,3220,827],{"class":641},[579,3222,2281],{"class":637},[579,3224,847],{"class":641},[579,3226,3227],{"class":581,"line":593},[579,3228,621],{"emptyLinePlaceholder":620},[579,3230,3231,3233,3236,3239,3242,3244,3246,3249],{"class":581,"line":601},[579,3232,938],{"class":937},[579,3234,3235],{"class":941}," orders",[579,3237,3238],{"class":641}," =",[579,3240,3241],{"class":941}," orderStream",[579,3243,827],{"class":641},[579,3245,3058],{"class":589},[579,3247,3248],{"class":641},"();",[579,3250,3251],{"class":627},"       \u002F\u002F KGroupedStream\u003CString, Order>\n",[579,3253,3254,3256,3259,3261,3264,3266,3268,3270],{"class":581,"line":609},[579,3255,938],{"class":937},[579,3257,3258],{"class":941}," payments",[579,3260,3238],{"class":641},[579,3262,3263],{"class":941}," paymentStream",[579,3265,827],{"class":641},[579,3267,3058],{"class":589},[579,3269,3248],{"class":641},[579,3271,3272],{"class":627},"   \u002F\u002F KGroupedStream\u003CString, Payment>\n",[579,3274,3275],{"class":581,"line":617},[579,3276,621],{"emptyLinePlaceholder":620},[579,3278,3279,3281,3283,3285,3287,3290,3292,3295],{"class":581,"line":624},[579,3280,1625],{"class":637},[579,3282,1365],{"class":641},[579,3284,783],{"class":937},[579,3286,975],{"class":641},[579,3288,3289],{"class":937}," CustomerSummary",[579,3291,1522],{"class":641},[579,3293,3294],{"class":941}," summary",[579,3296,945],{"class":641},[579,3298,3299],{"class":581,"line":631},[579,3300,3116],{"class":637},[579,3302,3303,3305,3307,3310,3313,3315,3317,3319,3322,3324,3326,3328,3330,3332,3334,3336],{"class":581,"line":645},[579,3304,3121],{"class":641},[579,3306,3124],{"class":589},[579,3308,3309],{"class":641},"((",[579,3311,3312],{"class":637},"key",[579,3314,975],{"class":641},[579,3316,1537],{"class":637},[579,3318,975],{"class":641},[579,3320,3321],{"class":637}," agg",[579,3323,981],{"class":641},[579,3325,984],{"class":641},[579,3327,3321],{"class":941},[579,3329,827],{"class":641},[579,3331,3135],{"class":589},[579,3333,692],{"class":641},[579,3335,972],{"class":637},[579,3337,3338],{"class":641},"))\n",[579,3340,3341,3343,3345,3347,3350,3352,3354,3356,3358,3360,3362,3364,3366,3368,3370,3372,3374,3376,3379],{"class":581,"line":656},[579,3342,3121],{"class":641},[579,3344,3124],{"class":589},[579,3346,692],{"class":641},[579,3348,3349],{"class":637},"payments",[579,3351,975],{"class":641},[579,3353,1567],{"class":641},[579,3355,3312],{"class":637},[579,3357,975],{"class":641},[579,3359,978],{"class":637},[579,3361,975],{"class":641},[579,3363,3321],{"class":637},[579,3365,981],{"class":641},[579,3367,984],{"class":641},[579,3369,3321],{"class":941},[579,3371,827],{"class":641},[579,3373,3154],{"class":589},[579,3375,692],{"class":641},[579,3377,3378],{"class":637},"payment",[579,3380,3338],{"class":641},[579,3382,3383,3385,3387],{"class":581,"line":662},[579,3384,3121],{"class":641},[579,3386,3164],{"class":589},[579,3388,653],{"class":641},[579,3390,3391,3394,3397],{"class":581,"line":677},[579,3392,3393],{"class":637},"            CustomerSummary",[579,3395,3396],{"class":585},"::new",[579,3398,964],{"class":641},[579,3400,3401,3404,3406,3408,3410,3412,3415,3417],{"class":581,"line":702},[579,3402,3403],{"class":941},"            Materialized",[579,3405,827],{"class":641},[579,3407,1910],{"class":589},[579,3409,692],{"class":641},[579,3411,1149],{"class":1148},[579,3413,3414],{"class":800},"customer-summary",[579,3416,1149],{"class":1148},[579,3418,1157],{"class":641},[320,3420,3421,2496,3423,2499,3425,3427,3428,1634,3431,3434,3435,3437,3438,3440,3441,827],{},[324,3422,3164],{},[324,3424,2306],{},[324,3426,2281],{},". Co-grouping can also be windowed — call ",[324,3429,3430],{},".windowedBy(TimeWindows...)",[324,3432,3433],{},".windowedBy(SessionWindows...)"," before ",[324,3436,3164],{},"; see ",[343,3439,125],{"href":126},". For the non-co-grouped single-stream folds, see ",[343,3442,120],{"href":121},[446,3444,3446],{"id":3445},"choosing-a-join","Choosing a join",[458,3448,3449,3462],{},[461,3450,3451],{},[464,3452,3453,3456,3459],{},[467,3454,3455],{},"You have",[467,3457,3458],{},"You want",[467,3460,3461],{},"Use",[477,3463,3464,3479,3492,3505,3518],{},[464,3465,3466,3469,3472],{},[482,3467,3468],{},"Two event streams, matches close in time",[482,3470,3471],{},"Correlate within a time window",[482,3473,3474,1567,3477,981],{},[355,3475,3476],{},"stream-stream",[324,3478,330],{},[464,3480,3481,3484,3487],{},[482,3482,3483],{},"An event stream + a lookup table",[482,3485,3486],{},"Enrich each event with current table state",[482,3488,3489],{},[355,3490,3491],{},"stream-table",[464,3493,3494,3497,3500],{},[482,3495,3496],{},"Two changelog tables, same key",[482,3498,3499],{},"A joined table that updates with either side",[482,3501,3502],{},[355,3503,3504],{},"table-table",[464,3506,3507,3510,3513],{},[482,3508,3509],{},"Two tables joined on a value field",[482,3511,3512],{},"Relational-style FK lookup",[482,3514,3515],{},[355,3516,3517],{},"foreign-key",[464,3519,3520,3523,3526],{},[482,3521,3522],{},"Several grouped streams, one key",[482,3524,3525],{},"Fold all of them into one aggregate",[482,3527,3528],{},[355,3529,3003],{},[446,3531,3533],{"id":3532},"where-to-go-next","Where to go next",[363,3535,3536,3541,3546,3551,3556],{},[366,3537,3538,3540],{},[343,3539,120],{"href":121}," — group and fold a single stream or table",[366,3542,3543,3545],{},[343,3544,125],{"href":126}," — windowed aggregations and windowed co-grouping",[366,3547,3548,3550],{},[343,3549,903],{"href":146}," — the serializers join stores need for keys and both value sides",[366,3552,3553,3555],{},[343,3554,100],{"href":101}," — store types behind join state",[366,3557,3558,3560],{},[343,3559,67],{"href":68}," — what drives stream-stream window closing",[3562,3563,3564],"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 .s-TwI, html code.shiki .s-TwI{--shiki-default:#2F798A}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 .si04Y, html code.shiki .si04Y{--shiki-default:#AB5959}html pre.shiki code .svycV, html code.shiki .svycV{--shiki-default:#B07D48}html pre.shiki code .sSP4y, html code.shiki .sSP4y{--shiki-default:#B5695977}html pre.shiki code .sUxyF, html code.shiki .sUxyF{--shiki-default:#2E8F82}",{"title":575,"searchDepth":593,"depth":593,"links":3566},[3567,3568,3571,3572,3573,3574,3575,3576],{"id":448,"depth":593,"text":449},{"id":526,"depth":593,"text":527,"children":3569},[3570],{"id":1215,"depth":601,"text":1216},{"id":1614,"depth":593,"text":1615},{"id":1999,"depth":593,"text":2000},{"id":2505,"depth":593,"text":2506},{"id":3003,"depth":593,"text":3004},{"id":3445,"depth":593,"text":3446},{"id":3532,"depth":593,"text":3533},"Combine two streams or tables on a shared key — stream-stream windowed joins, stream-table enrichment lookups, table-table joins, foreign-key joins, and co-grouping.","md",{},{"icon":133},"how-to",{"title":130,"description":3577},"vm8HGOTrNyHcP6Vfn8fyDpzlDBjrhNa3mG0WFEl7iUU",[3585,3587],{"title":125,"path":126,"stem":127,"description":3586,"icon":128,"children":-1},"Windowed aggregations in StoatFlow — tumbling, hopping, sliding, and session windows; emit strategies, suppression, Windowed keys, and WindowedSerdes.",{"title":135,"path":136,"stem":137,"description":3588,"icon":138,"children":-1},"Write custom record-by-record logic with Processor and FixedKeyProcessor — forward, access state, schedule punctuators, and register key-based timers.",1783713359097]