apache/beam · error · java.lang.IllegalArgumentException
WindowFns must match for a bounded-vs-bounded/unbounded-vs-u
Error message
WindowFns must match for a bounded-vs-bounded/unbounded-vs-unbounded join.
What it means
BeamCoGBKJoinRel.expand joins two PCollections via CoGroupByKey, which requires both inputs to use compatible windowing. It calls WindowFn.verifyCompatibility on the left and right window functions and, if they are incompatible, throws IllegalArgumentException explaining that window functions must match for a join. This guarantees elements land in the same windows on both sides so the join is well-defined.
Source
Thrown at sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/rel/BeamCoGBKJoinRel.java:128
"right_TimestampCombiner",
Window.<Row>configure().withTimestampCombiner(TimestampCombiner.EARLIEST));
// extract the join fields
List<Pair<RexNode, RexNode>> pairs = extractJoinRexNodes(condition);
int leftRowColumnCount = BeamSqlRelUtils.getBeamRelInput(left).getRowType().getFieldCount();
FieldAccessDescriptor leftKeyFields =
BeamJoinTransforms.getJoinColumns(true, pairs, 0, leftSchema);
FieldAccessDescriptor rightKeyFields =
BeamJoinTransforms.getJoinColumns(false, pairs, leftRowColumnCount, rightSchema);
WindowFn leftWinFn = leftRows.getWindowingStrategy().getWindowFn();
WindowFn rightWinFn = rightRows.getWindowingStrategy().getWindowFn();
try {
leftWinFn.verifyCompatibility(rightWinFn);
} catch (IncompatibleWindowException e) {
throw new IllegalArgumentException(
"WindowFns must match for a bounded-vs-bounded/unbounded-vs-unbounded join.", e);
}
verifySupportedTrigger(leftRows);
verifySupportedTrigger(rightRows);
return standardJoin(leftRows, rightRows, leftKeyFields, rightKeyFields);
}
}
private <T> void verifySupportedTrigger(PCollection<T> pCollection) {
WindowingStrategy windowingStrategy = pCollection.getWindowingStrategy();
if (UNBOUNDED.equals(pCollection.isBounded()) && !triggersOncePerWindow(windowingStrategy)) {
throw new UnsupportedOperationException(
"Joining unbounded PCollections is currently only supported for "
+ "non-global windows with triggers that are known to produce output once per window,"
+ "such as the default trigger with zero allowed lateness. "View on GitHub (pinned to 12126d8942)
Solutions
- Apply the same WindowFn to both join inputs before the join (e.g. Window.into(FixedWindows.of(Duration.standardMinutes(1))) on both sides)
- If one side is a bounded lookup table, window it with the same WindowFn as the streaming side (or convert the join to use global windows only if both are bounded and it's semantically safe)
- Reorder the pipeline so windowing is applied before the SQL JOIN transform
- Check WindowingStrategy of both PCollections (getWindowingStrategy().getWindowFn()) and make verifyCompatibility pass before invoking the SQL join
Example fix
// before PCollection<Row> right = rows.apply(Window.into(FixedWindows.of(Duration.standardMinutes(5)))); // after (match the left side) PCollection<Row> right = rows.apply(Window.into(FixedWindows.of(Duration.standardMinutes(1))));
Defensive patterns
Strategy: validation
Validate before calling
try {
left.getWindowingStrategy().getWindowFn().verifyCompatibility(right.getWindowingStrategy().getWindowFn());
} catch (IncompatibleWindowException e) {
throw new IllegalStateException("Join inputs use incompatible WindowFns: " + e.getMessage());
} Try / catch
try {
return sqlEnv.executeJoin(left, right);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("WindowFns must match")) {
// re-window both sides with the same WindowFn and retry
} else { throw e; }
} Prevention
- Always apply the same Window.into(...) to both sides of a SQL join on windowed data
- Check getWindowingStrategy().getWindowFn() equality/compatibility of both PCollections before joining
- Avoid mixing bounded (global window) and windowed streaming inputs in joins
- Centralize window configuration so all joins derive their WindowFn from one constant
When it happens
Trigger: Executing a Beam SQL JOIN where one side of the join uses a different WindowFn than the other — e.g. fixed windows of 1 minute vs 5 minutes, or sliding vs fixed windows, or global window vs fixed windows — on bounded or unbounded PCollections.
Common situations: Joining a streaming table (windowed with WithTimestamps/Window.into) with a bounded side table that stayed in the GlobalWindows; two streams windowed with different durations or window types being joined in SQL; changing one input's windowing after building the pipeline.
Related errors
- Joining unbounded PCollections is currently only supported f
- GroupByKey cannot be applied to non-bounded PCollection in t
- TVFSlidingWindow does not support side input windows.
- Runner does not support draining.
- Cannot encode null window
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/884f4118583db7c6.
Report an issue: GitHub.