apache/pulsar · error · IllegalStateException

Transaction pending ack replay error with illegal state :

Error message

Transaction pending ack replay error with illegal state : 

What it means

During transaction pending-ack replay, MLPendingAckReplyCallBack.handleMetadataEntry applies each replayed pending-ack metadata operation (e.g. commit, abort, individual ack recovery) to the pending ack handle. A switch over the operation type has no default case matching, meaning the log contains a PendingAckOp unknown/unexpected for this state. It throws IllegalStateException to fail-fast rather than silently dropping replay operations, which would corrupt pending-ack state.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/transaction/pendingack/impl/MLPendingAckReplyCallBack.java:123

                            if (pendingAckMetadata.getAckSetsCount() > 0) {
                                ackSets = new long[pendingAckMetadata.getAckSetsCount()];
                                for (int i = 0; i < pendingAckMetadata.getAckSetsCount(); i++) {
                                    ackSets[i] = pendingAckMetadata.getAckSetAt(i);
                                }
                            } else {
                                ackSets = new long[0];
                            }
                            Position position =
                                    AckSetStateUtil.createPositionWithAckSet(pendingAckMetadata.getLedgerId(),
                                            pendingAckMetadata.getEntryId(), ackSets);
                            positions.add(new MutablePair<>(position, pendingAckMetadata.getBatchSize()));
                        }
                    });
                    pendingAckHandle.handleIndividualAckRecover(txnID, positions);
                }
                break;
            default:
                throw new IllegalStateException("Transaction pending ack replay "
                        + "error with illegal state : " + pendingAckMetadataEntry.getPendingAckOp());

        }
    }
}

View on GitHub (pinned to 820761864e)

Solutions

  1. Inspect the pendingAckOp value in the metadata entry and confirm which broker version wrote it
  2. Verify all brokers in the cluster run a version that understands all PendingAckOp types present in the log
  3. Check the pending-ack ledger for corruption; if entries are corrupt, follow Pulsar runBookie/consistency recovery procedures for that managed ledger
  4. If caused by a version skew, roll forward (or uniformly roll back) all brokers, then replay
Defensive patterns

Strategy: try-catch

Validate before calling

// Before replay, ensure broker versions are uniform:
// check cluster version consistency and that the pending-ack ledger is readable
// No in-process pre-check exists; validate via ledger entry read before replay.

Try / catch

try {
    callBack.handleMetadataEntry(entry);
} catch (IllegalStateException e) {
    log.error("Pending-ack replay failed on entry: {}", entry.getEntryId(), e);
    // halt replay and inspect the pending-ack ledger; do not continue with partial state
    throw new TransactionPendingAckReplayException(e);
}

Prevention

When it happens

Trigger: Replaying a transaction pending-ack metadata entry whose PendingAckOp is not one of the handled cases — i.e. a corrupt or truncated bookkeeper entry, or a PendingAckOp written by a newer broker version being replayed by an older one.

Common situations: Upgrading/downgrading brokers in a cluster where the pending-ack log format changed; corrupted ledger entries after an unclean shutdown or disk issue; replaying a transaction log snapshot produced by different code paths.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/14ebc06e55c2f95b. Report an issue: GitHub.