apache/beam · error · IllegalStateException
Expected state to be STARTED, but was COMPLETE_SUCCESS
Error message
Expected state to be STARTED, but was COMPLETE_SUCCESS
What it means
AttemptState.checkStarted() throws IllegalStateException when the attempt already finished successfully (COMPLETE_SUCCESS). A completed attempt can no longer be used to start or continue RPC work. This prevents double-completion and stale attempts from being reused after a successful operation.
Source
Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/firestore/RpcQosImpl.java:204
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(
"initialization.fields.uninitialized") // allow transient fields to be managed by component
// lifecycleView on GitHub (pinned to 12126d8942)
Solutions
- Create a new attempt via qos.newAttempt() for every operation; never reuse a completed attempt.
- Call checkStarted() only once per attempt, immediately after start().
- Scope the Attempt to a single logical RPC and discard it after completion.
- Prefer the built-in Firestore connector transforms that handle attempt lifecycle internally.
Example fix
// before RpcQos.Attempt attempt = qos.newAttempt(); attempt.start(); attempt.completeSuccessfully(); attempt.checkStarted(); // throws: already completed // after RpcQos.Attempt attempt = qos.newAttempt(); attempt.start(); attempt.completeSuccessfully(); attempt = qos.newAttempt(); // fresh attempt for the next operation
Defensive patterns
Strategy: try-catch
Validate before calling
// Java: scope each Attempt to exactly one operation
for (Write write : writes) {
RpcQos.Attempt attempt = qos.newAttempt();
attempt.start();
attempt.checkStarted();
// perform RPC
attempt.completeSuccessfully();
} Type guard
boolean canStartRpc(RpcQos.Attempt attempt, boolean alreadyCompleted) {
return !alreadyCompleted;
} Try / catch
try {
attempt.checkStarted();
// proceed
} catch (IllegalStateException e) {
attempt = qos.newAttempt(); // replace completed attempt
attempt.start();
} Prevention
- Use one Attempt per RPC; never loop over an attempt completed earlier.
- Treat completeSuccessfully() as terminal for that attempt object.
- Prefer connector-managed transforms over manual Qos usage.
When it happens
Trigger: Calling checkStarted() after completeSuccessfully() — e.g. attempting to run another RPC on the same attempt, or re-entering a batch flow with a finished attempt object.
Common situations: Reusing an Attempt across multiple batched writes; loops that forget to create a new attempt per iteration; shared Attempt instances used by multiple pipeline steps.
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
- Expected state to be PENDING or STARTED, but was COMPLETE_SU
- Expected state to be PENDING or STARTED, but was COMPLETE_ER
- Expected state to be STARTED, but was PENDING
- Error collections cannot be added after Error Handler is clo
- ErrorHandler must be finalized before the output can be retu
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/ff3c04560a2fa418.
Report an issue: GitHub.