apache/beam · error · IllegalStateException

Last attempted key was

Error message

Last attempted key was %s in range %s, claiming work in [%s, %s) was not attempted

What it means

ByteKeyRangeTracker.checkDone verifies that a splittable-DoFn claim covered the whole key range. If the last attempted key is still strictly inside the range (or the end key is empty/unbounded relative to it), Beam throws IllegalStateException saying work in [nextKey, endKey) was never claimed. It signals the tracker was marked done prematurely.

Solutions

  1. Continue claiming keys until the returned tryClaim result indicates the range is exhausted before calling markDone
  2. Verify your loop processes ByteKey.next(lastAttemptedKey) .. endKey; the message names the unclaimed interval
  3. Don't call checkDone/markDone when a split/checkpoint left part of the range unclaimed - that portion belongs to another split
  4. Ensure the range's endKey handling matches the empty-end-key (unbounded) semantics in your tracker usage

Example fix

// before
while (keys.hasNext()) {
  if (!tracker.tryClaim(keys.next())) break;
}
tracker.markDone(); // may throw: range not fully claimed

// after
ByteKey k = start;
while (true) {
  if (!tracker.tryClaim(k)) break; // false = range done
  process(k);
  k = ByteKey.next(k);
}
tracker.markDone(); // safe: all keys claimed or range exhausted
Defensive patterns

Strategy: try-catch

Validate before calling

// Before markDone, confirm the range is exhausted
if (tracker.currentRestriction().getEndKey().compareTo(lastClaimedKey) > 0
    && !lastClaimedKey.isEmpty()) {
  throw new IllegalStateException("Range not fully claimed: "
      + ByteKey.next(lastClaimedKey) + " .. "
      + tracker.currentRestriction().getEndKey());
}

Try / catch

try {
  tracker.markDone();
} catch (IllegalStateException e) {
  if (e.getMessage().contains("was not attempted")) {
    // continue claiming from the reported next key instead of failing the bundle
  }
}

Prevention

When it happens

Trigger: Calling tryClaim/trySplit such that markDone or checkDone is invoked while lastAttemptedKey < range.getEndKey() (and endKey not empty); e.g. stopping iteration over ByteKeys before reaching the end and then calling markDone().

Common situations: Custom splittable DoFn over byte-key ranges that breaks out of the claim loop early; runner-driven checkpoint tests where the restriction was split but completion was asserted; bugs in restriction tracking logic treating empty ranges incorrectly.

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

Appendix: source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/splittabledofn/ByteKeyRangeTracker.java:199

        lastAttemptedKey != null,
        "Key range is non-empty %s and no keys have been attempted.",
        range);

    // Return if the last attempted key was the empty key representing the end of range for
    // all ranges.
    if (lastAttemptedKey.isEmpty()) {
      return;
    }

    // The lastAttemptedKey is the last key of current restriction.
    if (!range.getEndKey().isEmpty() && next(lastAttemptedKey).compareTo(range.getEndKey()) >= 0) {
      return;
    }

    // If the last attempted key was not at or beyond the end of the range then throw.
    if (range.getEndKey().isEmpty() || range.getEndKey().compareTo(lastAttemptedKey) > 0) {
      ByteKey nextKey = next(lastAttemptedKey);
      throw new IllegalStateException(
          String.format(
              "Last attempted key was %s in range %s, claiming work in [%s, %s) was not attempted",
              lastAttemptedKey, range, nextKey, range.getEndKey()));
    }
  }

  @Override
  public IsBounded isBounded() {
    return IsBounded.BOUNDED;
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("range", range)
        .add("lastClaimedKey", lastClaimedKey)
        .add("lastAttemptedKey", lastAttemptedKey)
        .toString();

View on GitHub (pinned to 12126d8942)