apache/beam · critical · IllegalStateException

sideInputAccessor (transient field) is null

Error message

sideInputAccessor (transient field) is null

What it means

DynamicDestinations.sideInput accesses a side input view, but the transient sideInputAccessor field is only wired up when a DoFn ProcessContext is available (setSideInputAccessorFromProcessContext). If sideInput() is called before the accessor is set — typically at pipeline-construction time or outside a DoFn lifecycle — it throws IllegalStateException.

Solutions

  1. Only call sideInput() from methods invoked during bundle processing (e.g. inside getDestination called at runtime via DynamicDestinations.Transforms), not from code running at construction time
  2. Ensure setSideInputAccessorFromProcessContext is invoked with the current ProcessContext before use
  3. Move side-input-dependent logic into the destination/table callbacks that execute per element
  4. In tests, set the accessor manually with a mock ProcessContext

Example fix

// before: side input read during pipeline construction
@Override
public TableDestination getDestination(ValueInSingleWindow<Element> element) {
  String region = getSideInput(regionView).get(...); // accessor null at construction
  ...
}
// after: read from element/window context at process time
@Override
public TableDestination getDestination(ValueInSingleWindow<Element> element) {
  String region = element.getOperand() != null
      ? computeRegion(element.getOperand())
      : getSideInput(regionView).get(element.getWindow());
  ...
}
Defensive patterns

Strategy: validation

Validate before calling

if (dynamicDestinations.getPipelineOptions() == null && !isInBundleProcessingContext()) {
  throw new IllegalStateException("sideInput() only callable during bundle processing");
}

Type guard

boolean canUseSideInputs(DynamicDestinations<?, ?> dd) { return dd.getSideInputs() != null && dd.isSideInputAccessorAvailable(); }

Try / catch

try { region = getSideInput(view).get(window); } catch (IllegalStateException e) { LOG.error("Side input accessed at wrong lifecycle stage"); throw e; }

Prevention

When it happens

Trigger: Calling getSideInput/view inside getDestination(), getTable(), getFormatFunction(), or getDestinationCoder() that run during pipeline construction or in a non-DoFn context; calling sideInput from a worker thread outside the ProcessContext lifecycle; calling before startBundle.

Common situations: Users attempting to resolve per-destination side-input values in DynamicDestinations.getDestination at graph-building time; calling sideInput in tests without a DoFn.ProcessContext; serializing/deserializing the DynamicDestinations where the transient field is lost.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigquery/DynamicDestinations.java:121

  /**
   * Specifies that this object needs access to one or more side inputs. This side inputs must be
   * globally windowed, as they will be accessed from the global window.
   */
  public List<PCollectionView<?>> getSideInputs() {
    return Lists.newArrayList();
  }

  /**
   * Returns the value of a given side input. The view must be present in {@link #getSideInputs()}.
   */
  protected final <SideInputT> SideInputT sideInput(PCollectionView<SideInputT> view) {
    checkState(
        getSideInputs().contains(view),
        "View %s not declared in getSideInputs() (%s)",
        view,
        getSideInputs());
    if (sideInputAccessor == null) {
      throw new IllegalStateException("sideInputAccessor (transient field) is null");
    }
    return sideInputAccessor.sideInput(view);
  }

  void setSideInputAccessorFromProcessContext(DoFn<?, ?>.ProcessContext context) {
    this.sideInputAccessor = new SideInputAccessorViaProcessContext(context);
    this.options = context.getPipelineOptions();
  }

  /**
   * Returns an object that represents at a high level which table is being written to. May not
   * return null.
   *
   * <p>The method must return a unique object for different destination tables involved over all
   * BigQueryIO write transforms in the same pipeline. See
   * https://github.com/apache/beam/issues/32335 for details.
   */
  public abstract DestinationT getDestination(@Nullable ValueInSingleWindow<T> element);

View on GitHub (pinned to 12126d8942)