apache/pulsar · error · IndexOutOfBoundsException

fromIndex < -1: <fromIndex>

Error message

fromIndex < -1: <fromIndex>

What it means

BitSetRecyclable.previousSetBit(int fromIndex) returns the nearest set bit at or before fromIndex; it deliberately allows fromIndex == -1 (returns -1 as the exhausted sentinel) but throws IndexOutOfBoundsException for any index < -1. This mirrors java.util.BitSet's JDK 1.7 semantics, so only genuinely corrupted cursors (<= -2) trigger the throw.

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/util/collections/BitSetRecyclable.java:765

     * use the following loop:
     *
     *  <pre> {@code
     * for (int i = bs.length(); (i = bs.previousSetBit(i-1)) >= 0; ) {
     *     // operate on index i here
     * }}</pre>
     *
     * @param  fromIndex the index to start checking from (inclusive)
     * @return the index of the previous set bit, or {@code -1} if there
     *         is no such bit
     * @throws IndexOutOfBoundsException if the specified index is less
     *         than {@code -1}
     * @since  1.7
     */
    public int previousSetBit(int fromIndex) {
        if (fromIndex < 0) {
            if (fromIndex == -1)
                return -1;
            throw new IndexOutOfBoundsException(
                "fromIndex < -1: " + fromIndex);
        }

        checkInvariants();

        int u = wordIndex(fromIndex);
        if (u >= wordsInUse)
            return length() - 1;

        long word = words[u] & (WORD_MASK >>> -(fromIndex+1));

        while (true) {
            if (word != 0)
                return (u+1) * BITS_PER_WORD - 1 - Long.numberOfLeadingZeros(word);
            if (u-- == 0)
                return -1;
            word = words[u];
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Stop backward iteration when previousSetBit returns -1
  2. Guard: only call when fromIndex >= -1
  3. Fix the cursor arithmetic that produced values below -1
  4. Add a boundary test starting previousSetBit from 0 and walking down

Example fix

// before
int prev = bs.previousSetBit(cursor); // cursor may be < -1
// after
int prev = cursor < -1 ? -1 : bs.previousSetBit(cursor);
if (prev < 0) break;
Defensive patterns

Strategy: validation

Validate before calling

if (fromIndex < -1) {
    throw new IllegalArgumentException("fromIndex must be >= -1, got " + fromIndex);
}
int prev = ackSet.previousSetBit(fromIndex);

Type guard

static boolean isBackwardScanStart(int idx) { return idx >= -1; }

Try / catch

for (int i = ackSet.previousSetBit(start); i >= 0; i = ackSet.previousSetBit(i - 1)) {
    // process i
} // -1 terminates naturally and is a legal input

Prevention

When it happens

Trigger: Calling previousSetBit with an index <= -2, e.g. continuing a backward loop past the -1 sentinel, or passing an uninitialized/error value like Integer.MIN_VALUE or a wrapped-around subtraction result.

Common situations: Backward scans over ack sets where the loop doesn't stop at -1; index arithmetic like idx-2 from a 0 start; using an error-code return as the next cursor.

Related errors


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