apache/beam · error · IllegalStateException

Expected state to be STARTED, but was PENDING

Error message

Expected state to be STARTED, but was PENDING

What it means

AttemptState.checkStarted() asserts that an attempt has actually begun (STARTED) before an operation proceeds. Calling it while the attempt is still PENDING (start() was never invoked) throws IllegalStateException. The Qos framework requires start() to be called — which also reserves budget — before RPC work is attempted.

Source

Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/firestore/RpcQosImpl.java:202

      switch (this) {
        case PENDING:
        case STARTED:
          return;
        case COMPLETE_SUCCESS:
          throw new IllegalStateException(
              "Expected state to be PENDING or STARTED, but was COMPLETE_SUCCESS");
        case COMPLETE_ERROR:
          throw new IllegalStateException(
              "Expected state to be PENDING or STARTED, but was COMPLETE_ERROR");
      }
    }

    public void checkStarted() {
      switch (this) {
        case STARTED:
          return;
        case PENDING:
          throw new IllegalStateException("Expected state to be STARTED, but was PENDING");
        case COMPLETE_SUCCESS:
          throw new IllegalStateException("Expected state to be STARTED, but was COMPLETE_SUCCESS");
        case COMPLETE_ERROR:
          throw new IllegalStateException("Expected state to be STARTED, but was COMPLETE_ERROR");
      }
    }
  }

  private abstract class BaseRpcAttempt implements RpcAttempt {
    private final Logger logger;
    final O11y o11y;
    final StatusCodeAwareBackoff backoff;
    final Sleeper sleeper;

    AttemptState state;
    Instant start;

    @SuppressWarnings(

View on GitHub (pinned to 12126d8942)

Solutions

  1. Call attempt.start() after newAttempt() and before using the attempt for RPC operations.
  2. Ensure no early return skips start() between creation and first use of the attempt.
  3. Use the connector's high-level helpers (e.g. FirestoreV1 write transforms) which manage the attempt lifecycle for you.
  4. If using checkActive() vs checkStarted() deliberately, switch to checkActive() when PENDING is an acceptable state.

Example fix

// before
RpcQos.Attempt attempt = qos.newAttempt();
attempt.checkStarted(); // throws: still PENDING
// after
RpcQos.Attempt attempt = qos.newAttempt();
attempt.start();
attempt.checkStarted(); // OK
Defensive patterns

Strategy: validation

Validate before calling

// Java: ensure start() was called before checkStarted()
RpcQos.Attempt attempt = qos.newAttempt();
attempt.start(); // must precede checkStarted()/RPC use
attempt.checkStarted();

Type guard

boolean attemptStarted = false; // track alongside the attempt
boolean readyForRpc(RpcQos.Attempt attempt) {
  return attemptStarted;
}

Try / catch

try {
  attempt.checkStarted();
  // perform batched write / RPC
} catch (IllegalStateException e) {
  LOG.error("Attempt was not started; call attempt.start() after newAttempt()", e);
}

Prevention

When it happens

Trigger: Calling attempt.checkStarted() (or APIs that call it internally, like the write batch path) on an attempt created with qos.newAttempt() but never started with attempt.start().

Common situations: Skipping the start() call when writing custom Firestore sink code; refactoring that removed the start() invocation; early exits between newAttempt() and start() followed by an attempt to use the batch.

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