apache/beam · error · java.lang.UnsupportedOperationException

Side input join can only be used if one table is bounded.

Error message

Side input join can only be used if one table is bounded.

What it means

The side-input join implementation requires exactly one side of the join to be bounded: the bounded side is materialized as a side input while the other side streams. If both inputs are unbounded (both streaming), side-input join cannot be used and Beam SQL throws this UnsupportedOperationException during translation.

Source

Thrown at sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/rel/BeamSideInputJoinRel.java:118

    // 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
              .get(0)
              .apply(
                  "left_TimestampCombiner",
                  Window.<Row>configure().withTimestampCombiner(TimestampCombiner.EARLIEST));
      PCollection<Row> rightRows =

View on GitHub (pinned to 12126d8942)

Solutions

  1. Make one side bounded (e.g. read a static reference table from a file/DB) so the side-input join precondition holds.
  2. Use a true streaming join construct instead: window both PCollections and use CoGroupByKey, or rewrite the query so Calcite selects a streaming-capable join strategy.
  3. Window both sides identically and join within windows if the sources are both unbounded.
  4. Check the physical plan and add hints/constraints so the join rule does not pick BeamSideInputJoinRel for two unbounded inputs.

Example fix

-- before
SELECT * FROM stream1 JOIN stream2 ON stream1.k = stream2.k
-- after
SELECT * FROM stream1 JOIN bounded_lookup_table ON stream1.k = bounded_lookup_table.k
Defensive patterns

Strategy: validation

Validate before calling

if (left.isBounded() == IsBounded.UNBOUNDED && right.isBounded() == IsBounded.UNBOUNDED) { throw new IllegalArgumentException("Side-input join requires one bounded table; use CoGroupByKey/windowed join for stream-stream"); }

Type guard

boolean oneBounded = left.isBounded() != right.isBounded();

Try / catch

try { pipeline.apply(SqlTransform.query(sql)); } catch (UnsupportedOperationException e) { if (e.getMessage().contains("one table is bounded")) { /* switch to windowed CoGroup join */ } else { throw e; } }

Prevention

When it happens

Trigger: BeamSideInputJoinRel.buildPTransform detects leftRelNode.isBounded()==UNBOUNDED && rightRelNode.isBounded()==UNBOUNDED, i.e. a streaming-streaming join planned as a side-input join.

Common situations: Joining two streaming sources (e.g. two Pub/Sub topics) in Beam SQL and expecting the side-input optimization to apply; users often think side-input join is the general streaming join, but it is only for one-batched-side joins.

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/bf2cbed34e13fb1f. Report an issue: GitHub.