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
- Continue claiming keys until the returned tryClaim result indicates the range is exhausted before calling markDone
- Verify your loop processes ByteKey.next(lastAttemptedKey) .. endKey; the message names the unclaimed interval
- Don't call checkDone/markDone when a split/checkpoint left part of the range unclaimed - that portion belongs to another split
- 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
- Always loop until tryClaim returns false before calling markDone
- Never break out of claim loops early; leave unclaimed work via trySplit for redistribution
- Write unit tests mirroring testTryClaim/testCheckpointUnstarted for custom key-range trackers
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
- Expected size >= 0 but received
- RecordId unsupported in
- RecordOffset unsupported in
- Restriction unsupported in
- RestrictionTracker unsupported in
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)