{"record":{"id":"ff3c04560a2fa418","repo":"apache/beam","slug":"expected-state-to-be-started-but-was-complete-success","errorCode":null,"errorMessage":"Expected state to be STARTED, but was COMPLETE_SUCCESS","messagePattern":"Expected state to be STARTED, but was COMPLETE_SUCCESS","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/firestore/RpcQosImpl.java","lineNumber":204,"sourceCode":"        case STARTED:\n          return;\n        case COMPLETE_SUCCESS:\n          throw new IllegalStateException(\n              \"Expected state to be PENDING or STARTED, but was COMPLETE_SUCCESS\");\n        case COMPLETE_ERROR:\n          throw new IllegalStateException(\n              \"Expected state to be PENDING or STARTED, but was COMPLETE_ERROR\");\n      }\n    }\n\n    public void checkStarted() {\n      switch (this) {\n        case STARTED:\n          return;\n        case PENDING:\n          throw new IllegalStateException(\"Expected state to be STARTED, but was PENDING\");\n        case COMPLETE_SUCCESS:\n          throw new IllegalStateException(\"Expected state to be STARTED, but was COMPLETE_SUCCESS\");\n        case COMPLETE_ERROR:\n          throw new IllegalStateException(\"Expected state to be STARTED, but was COMPLETE_ERROR\");\n      }\n    }\n  }\n\n  private abstract class BaseRpcAttempt implements RpcAttempt {\n    private final Logger logger;\n    final O11y o11y;\n    final StatusCodeAwareBackoff backoff;\n    final Sleeper sleeper;\n\n    AttemptState state;\n    Instant start;\n\n    @SuppressWarnings(\n        \"initialization.fields.uninitialized\") // allow transient fields to be managed by component\n    // lifecycle","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/firestore/RpcQosImpl.java#L186-L222","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nRpcQos.Attempt attempt = qos.newAttempt();\nattempt.start();\nattempt.completeSuccessfully();\nattempt.checkStarted(); // throws: already completed\n// after\nRpcQos.Attempt attempt = qos.newAttempt();\nattempt.start();\nattempt.completeSuccessfully();\nattempt = qos.newAttempt(); // fresh attempt for the next operation","handlingStrategy":"try-catch","validationCode":"// Java: scope each Attempt to exactly one operation\nfor (Write write : writes) {\n  RpcQos.Attempt attempt = qos.newAttempt();\n  attempt.start();\n  attempt.checkStarted();\n  // perform RPC\n  attempt.completeSuccessfully();\n}","typeGuard":"boolean canStartRpc(RpcQos.Attempt attempt, boolean alreadyCompleted) {\n  return !alreadyCompleted;\n}","tryCatchPattern":"try {\n  attempt.checkStarted();\n  // proceed\n} catch (IllegalStateException e) {\n  attempt = qos.newAttempt(); // replace completed attempt\n  attempt.start();\n}","preventionTips":["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."],"tags":["firestore","qos","illegal-state","lifecycle","java","apache-beam"],"backgroundTag":"invalid-state-transition","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}