DrKLO/Telegram · error · IndexOutOfBoundsException
Index out of bounds - passed position = {}, new list size =
Error message
Index out of bounds - passed position = {}, new list size = {} What it means
The mirror of error 9: convertNewPositionToOld maps a position in the NEW list back to the old list. mNewItemStatuses was sized to mNewListSize at DiffResult construction. Passing newListPosition outside [0, mNewListSize) is out of contract and would index out of bounds, so DiffUtil throws. This usually means the new list shrank or the caller is using a stale/newer list's position against an older DiffResult.
Source
Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/DiffUtil.java:696
} 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.
*
* @see #NO_POSITION
* @see #convertOldPositionToNew(int)
*/
public int convertNewPositionToOld(@IntRange(from = 0) int newListPosition) {
if (newListPosition < 0 || newListPosition >= mNewListSize) {
throw new IndexOutOfBoundsException("Index out of bounds - passed position = "
+ newListPosition + ", new list size = " + mNewListSize);
}
final int status = mNewItemStatuses[newListPosition];
if ((status & FLAG_MASK) == 0) {
return NO_POSITION;
} else {
return status >> FLAG_OFFSET;
}
}
/**
* Finds a matching item that is before the given coordinates in the matrix
* (before : left and above).
*
* @param x The x position in the matrix (position in the old list)
* @param y The y position in the matrix (position in the new list)
* @param snakeIndex The current snake index
* @param removal True if we are looking for a removal, false otherwiseView on GitHub (pinned to 45ab8f4308)
Solutions
- Use positions strictly within [0, new list size) as captured at DiffResult creation.
- Recompute the diff on every data swap; never reuse a DiffResult after the backing list changes.
- Bounds-check against the size recorded at diff time before converting.
Example fix
// before
int oldPos = result.convertNewPositionToOld(newPos); // may be stale
// after
int newSize = newList.size();
int oldPos = (newPos >= 0 && newPos < newSize)
? result.convertNewPositionToOld(newPos)
: DiffUtil.DiffResult.NO_POSITION; Defensive patterns
Strategy: validation
Validate before calling
// Bounds-check against the new list size captured at diff time
int newSize = newSnap.size();
int safeConvertOld(DiffUtil.DiffResult r, int newPos) {
if (newPos < 0 || newPos >= newSize) return DiffUtil.DiffResult.NO_POSITION;
return r.convertNewPositionToOld(newPos);
} Prevention
- Track the new-list size at diff creation and clamp positions to it.
- Discard stale DiffResults after submitList.
- Avoid mixing old/new position indices in callbacks.
When it happens
Trigger: Querying convertNewPositionToOld with a position from a later list version after the new list was replaced; using the old list size as the upper bound by mistake; the new list was mutated post-diff (same root cause as error 8).
Common situations: Stale DiffResult reused after submitList; position arithmetic mixing old/new indices; adapter that reuses DiffResult across refresh cycles.
Related errors
- Index out of bounds - passed position = {}, old 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/f7b04702c3a64e65.
Report an issue: GitHub.