apache/beam · error · java.lang.UnsupportedOperationException

%s side of an OUTER JOIN must be a non Seekable table.

Error message

%s side of an OUTER JOIN must be a non Seekable table.

What it means

For side-input lookup joins, LEFT OUTER JOIN requires the left (outer) side to be the non-seekable table, and RIGHT OUTER JOIN requires the right side to be non-seekable — the seekable side is broadcast/looked up, and the outer side drives the stream. If the outer side is the seekable table, translation throws this UnsupportedOperationException.

Source

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

    super(cluster, traitSet, left, right, condition, variablesSet, joinType);
  }

  @Override
  public PTransform<PCollectionList<Row>, PCollection<Row>> buildPTransform() {
    // if one of the sides is Seekable & the other is non Seekable
    // then do a sideInputLookup join.
    // When doing a sideInputLookup join, the windowFn does not need to match.
    // Only support INNER JOIN & LEFT OUTER JOIN where left side of the join must be
    // non Seekable & RIGHT OUTER JOIN where right side of the join must be non Seekable
    if (joinType == JoinRelType.FULL) {
      throw new UnsupportedOperationException(
          "FULL OUTER JOIN is not supported when join "
              + "a Seekable table with a non Seekable table.");
    }

    if ((joinType == JoinRelType.LEFT && seekableInputIndex().get() == 0)
        || (joinType == JoinRelType.RIGHT && seekableInputIndex().get() == 1)) {
      throw new UnsupportedOperationException(
          String.format("%s side of an OUTER JOIN must be a non Seekable table.", joinType.name()));
    }
    return new SideInputLookupJoin();
  }

  private class SideInputLookupJoin extends PTransform<PCollectionList<Row>, PCollection<Row>> {

    @Override
    public PCollection<Row> expand(PCollectionList<Row> pinput) {
      Schema schema = CalciteUtils.toSchema(getRowType());

      BeamRelNode seekableRel =
          BeamSqlRelUtils.getBeamRelInput(getInput(seekableInputIndex().get()));
      BeamRelNode nonSeekableRel =
          BeamSqlRelUtils.getBeamRelInput(getInput(nonSeekableInputIndex().get()));

      // Offset field references according to which table is on the left
      int factColOffset =

View on GitHub (pinned to 12126d8942)

Solutions

  1. Swap the join operands so the non-seekable table is on the OUTER side: RIGHT OUTER JOIN with the seekable table on the left, or LEFT OUTER JOIN with it on the right.
  2. Verify which input is Seekable (seekableInputIndex) and orient the outer join accordingly.
  3. Use an INNER JOIN, which has no outer-side orientation requirement, if unmatched rows aren't needed.
  4. Fall back to a standard join (non side-input-lookup) by making the lookup table a regular bounded input.

Example fix

-- before
SELECT * FROM seekable_table LEFT OUTER JOIN stream_table ON seekable_table.k = stream_table.k
-- after
SELECT * FROM stream_table RIGHT OUTER JOIN seekable_table ON stream_table.k = seekable_table.k
Defensive patterns

Strategy: validation

Validate before calling

if ((joinType == JoinRelType.LEFT && seekableIndex == 0) || (joinType == JoinRelType.RIGHT && seekableIndex == 1)) { throw new IllegalArgumentException("Outer side must be the non-Seekable table"); }

Type guard

boolean orientationOk = joinType != JoinRelType.LEFT || !isSeekable(left) && joinType != JoinRelType.RIGHT || !isSeekable(right);

Try / catch

try { pipeline.apply(SqlTransform.query(sql)); } catch (UnsupportedOperationException e) { if (e.getMessage().contains("must be a non Seekable table")) { /* swap join operands */ } else { throw e; } }

Prevention

When it happens

Trigger: BeamSideInputLookupJoinRel.buildPTransform sees joinType==LEFT with seekableInputIndex()==0 (left side seekable), or joinType==RIGHT with seekableInputIndex()==1 (right side seekable).

Common situations: Writing 'SELECT ... FROM seekable_table LEFT OUTER JOIN streaming_table ...' when the rule expects the reverse orientation; users often don't know which table is registered as Seekable.

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