DrKLO/Telegram · error · IndexOutOfBoundsException

Index out of bounds - passed position = {}, old list size =

Error message

Index out of bounds - passed position = {}, old list size = {}

What it means

After DiffUtil computes a result, convertOldPositionToNew maps a position in the OLD list to its new position (or NO_POSITION if removed). It reads mOldItemStatuses[oldListPosition], which was sized to mOldListSize at result construction. Passing oldListPosition >= mOldListSize or < 0 would read out of bounds, so DiffUtil guards explicitly. The exception means the caller is feeding a position that exceeds the old list size that the DiffResult was computed against — typically because the list changed between computing the diff and querying the result.

Source

Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/DiffUtil.java:672

                return; // already set by a latter item
            }
            findMatchingItem(x, y, snakeIndex, true);
        }

        /**
         * Given a position in the old list, returns the position in the new list, or
         * {@code NO_POSITION} if it was removed.
         *
         * @param oldListPosition Position of item in old list
         *
         * @return Position of item in new list, or {@code NO_POSITION} if not present.
         *
         * @see #NO_POSITION
         * @see #convertNewPositionToOld(int)
         */
        public int convertOldPositionToNew(@IntRange(from = 0) int oldListPosition) {
            if (oldListPosition < 0 || oldListPosition >= mOldListSize) {
                throw new IndexOutOfBoundsException("Index out of bounds - passed position = "
                        + oldListPosition + ", old list size = " + mOldListSize);
            }
            final int status = mOldItemStatuses[oldListPosition];
            if ((status & FLAG_MASK) == 0) {
                return NO_POSITION;
            } else {
                return status >> FLAG_OFFSET;
            }
        }

        /**
         * Given a position in the new list, returns the position in the old list, or
         * {@code NO_POSITION} if it was removed.
         *
         * @param newListPosition Position of item in new list
         *
         * @return Position of item in old list, or {@code NO_POSITION} if not present.
         *

View on GitHub (pinned to 45ab8f4308)

Solutions

  1. Only call convertOldPositionToNew with positions in [0, diffResult-specific oldListSize).
  2. Recompute the DiffResult whenever the source list changes; do not cache across mutations.
  3. Track the old list size at diff time and clamp/bounds-check before converting.

Example fix

// before
int newPos = result.convertOldPositionToNew(pos); // pos may exceed old size

// after
int oldSize = oldList.size();
int newPos = (pos >= 0 && pos < oldSize)
    ? result.convertOldPositionToNew(pos)
    : DiffUtil.DiffResult.NO_POSITION;
Defensive patterns

Strategy: validation

Validate before calling

// Bounds-check against the old list size captured at diff time
int oldSize = oldSnap.size();
int safeConvertNew(DiffUtil.DiffResult r, int oldPos) {
    if (oldPos < 0 || oldPos >= oldSize) return DiffUtil.DiffResult.NO_POSITION;
    return r.convertOldPositionToNew(oldPos);
}

Prevention

When it happens

Trigger: Computing a DiffResult against list A of size N, then querying convertOldPositionToNew with position >= N after the list grew; off-by-one in a loop using the new list size instead of the old; caching a DiffResult and reusing it after the source list mutated.

Common situations: Saving DiffResult across adapter.setList calls; chat app appending messages and then re-querying an old DiffResult; confusing old vs new position semantics when building move callbacks.

Related errors


AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14). Data as JSON: /api/errors/2ab44c8dae757b4b. Report an issue: GitHub.