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

  1. Clean the transactional state store so it contains a contiguous chain of txid states matching what the coordinator will request
  2. Ensure the coordinator's txid sequence is consecutive BigInteger values starting at INIT_TXID (txid = txid.add(ONE)) and never regenerate old txids
  3. 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

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


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)