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
- Only call convertOldPositionToNew with positions in [0, diffResult-specific oldListSize).
- Recompute the DiffResult whenever the source list changes; do not cache across mutations.
- 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
- Recompute the DiffResult whenever the source list changes.
- Keep a reference to the old-list size at diff time for bounds checks.
- Never reuse a DiffResult after the backing list mutates.
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
- Index out of bounds - passed position = {}, new list size =
- {} is not within 0 and {}
- DiffUtil hit an unexpected case while trying to calculate th
- unknown flag for pos {} {}
- should not dispatch add or move for pre layout
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/2ab44c8dae757b4b.
Report an issue: GitHub.