nathanmarz/storm · error · IllegalStateException
Expecting previous txid state to be the previous transaction
Error message
Expecting previous txid state to be the previous transaction
What it means
In strict-order mode, getState() requires that if a previous txid has state, the requested txid must be exactly prev+1 (txids are BigInteger and consecutive). A gap means transactions were skipped or state is corrupted, so it throws IllegalStateException.
Solutions
- Clean the transactional state store so it contains a contiguous chain of txid states matching what the coordinator will request
- Ensure the coordinator's txid sequence is consecutive BigInteger values starting at INIT_TXID (txid = txid.add(ONE)) and never regenerate old txids
- If you only need reads of previous state, use getStateOrNull/isCommitted instead of strict getStateOrCreate
Example fix
// before BigInteger next = BigInteger.valueOf(50); // prev stored state is 48 -> gap rotatingState.getStateOrCreate(next); // after BigInteger next = lastTxid.add(BigInteger.ONE); rotatingState.getStateOrCreate(next);
Defensive patterns
Strategy: validation
Validate before calling
BigInteger prev = getLatestStoredTxid();
if (prev != null && !txid.equals(prev.add(BigInteger.ONE))) {
LOG.error("txid {} does not follow stored prev {}", txid, prev);
return false;
} Type guard
boolean isConsecutive(BigInteger prev, BigInteger txid) {
return prev == null || prev.add(BigInteger.ONE).equals(txid);
} Try / catch
try {
state.getStateOrCreate(txid);
} catch (IllegalStateException e) {
LOG.error("Non-consecutive txid {} in strict rotating state", txid, e);
rebuildStateFromScratch();
} Prevention
- Generate txids strictly as previousTxid + 1 from INIT_TXID
- Never reuse or regenerate old txids after coordinator restart
- Clean stale state when switching between topology versions that number transactions differently
When it happens
Trigger: Calling getStateOrCreate(txid) with _strictOrder=true when prevMap.lastKey() (prev) exists but txid is not prev + 1 (e.g. txid = prev.subtract(1) on retry with stale state, or a jumped txid).
Common situations: Retrying an already-committed transaction with a stale/newer txid; state residue left after topology restart combined with coordinator re-emission; manual state surgery deleting one transaction's entry creating a gap; mixing old and new transactional metadata after a version change.
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
- Trying to initialize transaction for which there should be…
- Expecting tx state to be initialized in strict order but…
- Failed to get metadata for a transaction
- Cannot have one group have fixed parallelism of two…
- Combiner state updater should receive a single tuple…
AI-assisted analysis of nathanmarz/storm@cdb116e942 (2026-09-12).
Data as JSON: /api/errors/6dacd475399c7500.
Report an issue: GitHub.
Appendix: source
Thrown at storm-core/src/jvm/backtype/storm/transactional/state/RotatingTransactionalState.java:83
_curr.remove(txid);
_state.delete(txPath(txid));
}
}
public Object getState(BigInteger txid, StateInitializer init) {
if(!_curr.containsKey(txid)) {
SortedMap<BigInteger, Object> prevMap = _curr.headMap(txid);
SortedMap<BigInteger, Object> afterMap = _curr.tailMap(txid);
BigInteger prev = null;
if(!prevMap.isEmpty()) prev = prevMap.lastKey();
if(_strictOrder) {
if(prev==null && !txid.equals(TransactionalSpoutCoordinator.INIT_TXID)) {
throw new IllegalStateException("Trying to initialize transaction for which there should be a previous state");
}
if(prev!=null && !prev.equals(txid.subtract(BigInteger.ONE))) {
throw new IllegalStateException("Expecting previous txid state to be the previous transaction");
}
if(!afterMap.isEmpty()) {
throw new IllegalStateException("Expecting tx state to be initialized in strict order but there are txids after that have state");
}
}
Object data;
if(afterMap.isEmpty()) {
Object prevData;
if(prev!=null) {
prevData = _curr.get(prev);
} else {
prevData = null;
}
data = init.init(txid, prevData);
} else {
data = null;View on GitHub (pinned to cdb116e942)