apache/beam · error · IllegalArgumentException

No Op handler should not be closed. This implies this IO is

Error message

No Op handler should not be closed. This implies this IO is misconfigured.

What it means

NoOpErrorHandler.isClosed always throws IllegalArgumentException because a No-Op handler represents a deliberately absent error handler. Querying its lifecycle state means an IO asked for error-handler metadata it should never need, indicating the IO was misconfigured to use the No-Op handler as a real one.

Source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/errorhandling/ErrorHandler.java:228

      super(sinkTransform, pipeline, BadRecord.getCoder(pipeline));
    }
  }

  /**
   * A default, placeholder error handler that exists to allow usage of .addErrorCollection()
   * without effects. This enables more simple codepaths without checking for whether the user
   * configured an error handler or not.
   */
  @Internal
  class DefaultErrorHandler<ErrorT, OutputT extends POutput>
      implements ErrorHandler<ErrorT, OutputT> {

    @Override
    public void addErrorCollection(PCollection<ErrorT> errorCollection) {}

    @Override
    public boolean isClosed() {
      throw new IllegalArgumentException(
          "No Op handler should not be closed. This implies this IO is misconfigured.");
    }

    @Override
    public @Nullable OutputT getOutput() {
      throw new IllegalArgumentException(
          "No Op handler has no output. This implies this IO is misconfigured.");
    }

    @Override
    public void close() {
      throw new IllegalArgumentException(
          "No Op handler should not be closed. This implies this IO is misconfigured.");
    }
  }
}

View on GitHub (pinned to 12126d8942)

Solutions

  1. Use a real (Default)ErrorHandler if lifecycle queries are needed.
  2. Change the IO configuration to supply a genuine error handler.
  3. Remove/guard the isClosed() polling in generic code so it skips No-Op handlers.

Example fix

// before
if (errorHandler.isClosed()) { ... } // throws for NoOp
// after
if (!(errorHandler instanceof ErrorHandler.NoOpErrorHandler) && errorHandler.isClosed()) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

if (errorHandler instanceof ErrorHandler.NoOpErrorHandler) { /* skip lifecycle checks */ }

Type guard

boolean isRealHandler(ErrorHandler<?> h) {
  return !(h instanceof ErrorHandler.NoOpErrorHandler);
}

Try / catch

try {
  errorHandler.isClosed();
} catch (IllegalArgumentException e) {
  LOG.warn("No-Op handler queried for lifecycle; IO misconfigured", e);
}

Prevention

When it happens

Trigger: An IO connector or framework code calls isClosed() on ErrorHandler.getNoOpHandler() — e.g. lifecycle checks in generic error-handler plumbing applied to a pipeline built without a real error handler.

Common situations: Passing the No-Op singleton where a DefaultErrorHandler is expected; generic code paths that poll isClosed() on any ErrorHandler implementation; refactor after removing a real handler but leaving polling logic.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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