apache/pulsar · error · IndexOutOfBoundsException

toIndex < 0: <toIndex>

Error message

toIndex < 0: <toIndex>

What it means

In the same checkRange method, BitSetRecyclable throws IndexOutOfBoundsException("toIndex < 0: " + toIndex) when the upper bound of a bit range is negative, after the fromIndex check. Since fromIndex <= toIndex is enforced later, a negative toIndex almost always means both bounds are negative, but it is checked independently to give a precise message. flip/set/clear/get range variants all funnel through this guard.

Source

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

     * possibly using recalculateWordsInUse().
     * @param wordIndex the index to be accommodated.
     */
    private void expandTo(int wordIndex) {
        int wordsRequired = wordIndex+1;
        if (wordsInUse < wordsRequired) {
            ensureCapacity(wordsRequired);
            wordsInUse = wordsRequired;
        }
    }

    /**
     * Checks that fromIndex ... toIndex is a valid range of bit indices.
     */
    private static void checkRange(int fromIndex, int toIndex) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
        if (toIndex < 0)
            throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex);
        if (fromIndex > toIndex)
            throw new IndexOutOfBoundsException("fromIndex: " + fromIndex +
                " > toIndex: " + toIndex);
    }

    /**
     * Sets the bit at the specified index to the complement of its
     * current value.
     *
     * @param  bitIndex the index of the bit to flip
     * @throws IndexOutOfBoundsException if the specified index is negative
     * @since  1.4
     */
    public void flip(int bitIndex) {
        if (bitIndex < 0)
            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

        int wordIndex = wordIndex(bitIndex);

View on GitHub (pinned to 820761864e)

Solutions

  1. Validate bounds before the call: if (toIndex < 0) return/handle, or normalize empty ranges to (0, 0).
  2. For inclusive-style bounds converted to exclusive (toIndex = end + 1), ensure the conversion handles end = -1 (empty) by skipping the operation entirely.
  3. Check the arithmetic producing toIndex (additions with negative lengths, size()-1 on empty collections).
  4. If bounds come from external data, validate start >= 0 and end >= start against the actual bit-set capacity before calling.

Example fix

// before
int end = values.size() - 1; // values empty -> end = -1
bitSet.set(0, end);

// after
int end = values.size() - 1;
if (end >= 0) {
    bitSet.set(0, end);
}
Defensive patterns

Strategy: validation

Validate before calling

static void requireValidRange(int fromIndex, int toIndex) {
    if (fromIndex < 0 || toIndex < 0 || fromIndex > toIndex)
        throw new IllegalArgumentException("invalid range [" + fromIndex + ", " + toIndex + ")");
}

Type guard

static boolean isValidRange(int from, int to) { return from >= 0 && to >= from; }

Try / catch

try {
    bitSet.set(from, to);
} catch (IndexOutOfBoundsException e) {
    log.warn("Skipping invalid bit range [{}, {}): {}", from, to, e.getMessage());
    // treat as empty range or escalate to a data-corruption error
}

Prevention

When it happens

Trigger: Calling set(fromIndex, toIndex), clear(fromIndex, toIndex), flip(fromIndex, toIndex) or get(fromIndex, toIndex) with toIndex < 0 — e.g. toIndex computed as length + delta where delta is negative, or both indices taken from an uninitialized/empty result like (start, end) of an empty span.

Common situations: Slicing ranges from deserialized data where the length field was 0 or negative, producing toIndex = start + length < 0; passing (0, list.size() - 1) style bounds when the collection is empty (toIndex = -1); copying range logic between 0-based and 1-based systems.

Related errors


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