apache/beam · error · java.lang.UnsupportedOperationException
%s side of an OUTER JOIN must be Unbounded table.
Error message
%s side of an OUTER JOIN must be Unbounded table.
What it means
Apache Beam SQL throws this when a LEFT OUTER JOIN has a bounded (finite/batch) left table, or a RIGHT OUTER JOIN has a bounded right table, in the side-input join strategy. The side-input implementation materializes the bounded side and streams the unbounded side, so the outer side (whose rows must all be kept) must be the streaming/unbounded one. A bounded outer side would require buffering an infinite side, which this plan cannot do.
Source
Thrown at sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/rel/BeamSideInputJoinRel.java:113
public PTransform<PCollectionList<Row>, PCollection<Row>> buildPTransform() {
// if one of the sides is Bounded & the other is Unbounded
// then do a sideInput join.
// When doing a sideInput join, the windowFn does not need to match.
// Only support INNER JOIN & LEFT OUTER JOIN where left side of the join must be
// the unbounded & RIGHT OUTER JOIN where right side of the join must be the unbounded
if (joinType == JoinRelType.FULL) {
throw new UnsupportedOperationException(
"FULL OUTER JOIN is not supported when join "
+ "a bounded table with an unbounded table.");
}
BeamRelNode leftRelNode = BeamSqlRelUtils.getBeamRelInput(left);
BeamRelNode rightRelNode = BeamSqlRelUtils.getBeamRelInput(right);
if ((joinType == JoinRelType.LEFT && leftRelNode.isBounded() == PCollection.IsBounded.BOUNDED)
|| (joinType == JoinRelType.RIGHT
&& rightRelNode.isBounded() == PCollection.IsBounded.BOUNDED)) {
throw new UnsupportedOperationException(
String.format("%s side of an OUTER JOIN must be Unbounded table.", joinType.name()));
}
if (leftRelNode.isBounded() == IsBounded.UNBOUNDED
&& rightRelNode.isBounded() == IsBounded.UNBOUNDED) {
throw new UnsupportedOperationException(
"Side input join can only be used if one table is bounded.");
}
return new SideInputJoin();
}
private class SideInputJoin extends PTransform<PCollectionList<Row>, PCollection<Row>> {
@Override
public PCollection<Row> expand(PCollectionList<Row> pinput) {
Schema leftSchema = pinput.get(0).getSchema();
Schema rightSchema = pinput.get(1).getSchema();
PCollection<Row> leftRows =
pinputView on GitHub (pinned to 12126d8942)
Solutions
- Swap the join sides so the unbounded (streaming) table is on the OUTER side (use RIGHT JOIN of the bounded table to the streaming table, or vice versa).
- Ensure the outer-join side input is unbounded, e.g. it originates from a streaming source (unbounded PCollection).
- Use a FULL/INNER join or a different join strategy (e.g. BeamJoinRel alternatives or co-group) that supports the boundedness combination you need.
- If the data is truly batch, make both sides bounded and let the batch join path run instead of the side-input path.
Example fix
-- before SELECT * FROM bounded_table LEFT OUTER JOIN streaming_table ON ... -- after SELECT * FROM streaming_table RIGHT OUTER JOIN bounded_table ON ...
Defensive patterns
Strategy: validation
Validate before calling
if ((joinType == JoinRelType.LEFT && left.isBounded() == IsBounded.BOUNDED) || (joinType == JoinRelType.RIGHT && right.isBounded() == IsBounded.BOUNDED)) { throw new IllegalArgumentException("Outer side of OUTER JOIN must be the unbounded table"); } Type guard
boolean validOuterJoin = (joinType != JoinRelType.LEFT || left.isBounded() == IsBounded.UNBOUNDED) && (joinType != JoinRelType.RIGHT || right.isBounded() == IsBounded.UNBOUNDED);
Try / catch
try { pipeline.apply(SqlTransform.query(sql)); } catch (UnsupportedOperationException e) { if (e.getMessage().contains("OUTER JOIN must be Unbounded")) { /* rewrite join orientation */ } else { throw e; } } Prevention
- Keep the streaming (unbounded) table on the outer side of OUTER JOINs in BeamSql
- Check input boundedness (PCollection.isBounded()) before planning outer joins
- Prefer INNER JOIN when boundedness orientation is uncertain
- Explain the query plan to see which join rel (side-input vs regular) is chosen
When it happens
Trigger: Executing a BeamSql query whose chosen physical plan is BeamSideInputJoinRel where joinType==LEFT and the left BeamRelNode is bounded, or joinType==RIGHT and the right input is bounded; thrown from buildPTransform during pipeline translation.
Common situations: Mixing a bounded Pub/Sub-batch table with a streaming source in SQL 'SELECT ... FROM bounded LEFT OUTER JOIN streaming'; Calcite picking the side-input join rule because exactly one side is unbounded while the outer side happens to be the bounded one.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- FULL OUTER JOIN is not supported when join a bounded table w
- Side input join can only be used if one table is bounded.
- FULL OUTER JOIN is not supported when join a Seekable table
- %s side of an OUTER JOIN must be a non Seekable table.
- Operator %s is not supported in join condition
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/10d53e66f3226d9a.
Report an issue: GitHub.