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 =
          pinput

View on GitHub (pinned to 12126d8942)

Solutions

  1. 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).
  2. Ensure the outer-join side input is unbounded, e.g. it originates from a streaming source (unbounded PCollection).
  3. Use a FULL/INNER join or a different join strategy (e.g. BeamJoinRel alternatives or co-group) that supports the boundedness combination you need.
  4. 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

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


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/10d53e66f3226d9a. Report an issue: GitHub.