apache/beam · error · IllegalStateException

Error handler is already closed, and may not be closed twice

Error message

Error handler is already closed, and may not be closed twice

What it means

ErrorHandler.close() throws IllegalStateException when called on an already-closed handler. Closing flattens registered error collections into the sink output once; a second close would re-finalize state, so Beam treats double-close as a programming error rather than idempotently succeeding.

Source

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

    @Override
    public boolean isClosed() {
      return closed;
    }

    @Override
    public @Nullable OutputT getOutput() {
      if (!this.isClosed()) {
        throw new IllegalStateException(
            "ErrorHandler must be finalized before the output can be returned");
      }
      return sinkOutput;
    }

    @Override
    public void close() {
      if (closed) {
        throw new IllegalStateException(
            "Error handler is already closed, and may not be closed twice");
      }
      closed = true;
      PCollection<ErrorT> flattened;
      if (errorCollections.isEmpty()) {
        LOG.info("Empty list of error pcollections passed to ErrorHandler.");
        flattened = pipeline.apply(Create.empty(coder));
      } else {
        flattened = PCollectionList.of(errorCollections).apply(Flatten.pCollections());
      }
      LOG.debug(
          "{} error collections are being sent to {}",
          errorCollections.size(),
          sinkTransform.getName());
      String sinkTransformName = sinkTransform.getName();
      sinkOutput =
          flattened
              .apply(

View on GitHub (pinned to 12126d8942)

Solutions

  1. Close the handler exactly once — remove duplicate close() calls.
  2. Prefer try-with-resources and drop manual close() calls.
  3. Track closed state in application code if multiple code paths may finalize the handler.

Example fix

// before
try (ErrorHandler<BadRecord> h = errorHandler) {
  ...
  errorHandler.close(); // double close
}
// after
try (ErrorHandler<BadRecord> h = errorHandler) {
  ... // close happens automatically once
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!closedOnce) { errorHandler.close(); closedOnce = true; }

Type guard

class CloseOnce<T> implements AutoCloseable {
  private final ErrorHandler<T> delegate; private boolean closed = false;
  CloseOnce(ErrorHandler<T> d){this.delegate=d;}
  public void close(){ if(!closed){ delegate.close(); closed=true; } }
}

Try / catch

try {
  errorHandler.close();
} catch (IllegalStateException e) {
  LOG.warn("Handler already closed; ignoring", e);
}

Prevention

When it happens

Trigger: Calling close() twice on the same DefaultErrorHandler — commonly via try-with-resources plus a manual close() call, or a __exit__ path (Python interop / with-statement semantics) that closes the handler after user code already closed it.

Common situations: Combining with-statement usage with explicit close(); closing the handler in both an expand() helper and the caller; re-running a finalize method during retries of pipeline construction.

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