DrKLO/Telegram · error · IndexOutOfBoundsException

{} is not within 0 and {}

Error message

{} is not within 0 and {}

What it means

AsyncListUtil lazily loads data items in background tiles and exposes getItem(position) to the UI thread. mItemCount is the total data set size reported by the DataCallback.refreshData contract. Requesting a position < 0 or >= mItemCount is out of contract because the underlying tile grid only covers [0, mItemCount). The exception is a hard bounds check — there is no valid item outside that range, so the call cannot return data and fails fast instead of returning a wrong value.

Source

Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/AsyncListUtil.java:156

     * Returns the data item at the given position or <code>null</code> if it has not been loaded
     * yet.
     *
     * <p>
     * If this method has been called for a specific position and returned <code>null</code>, then
     * {@link ViewCallback#onItemLoaded(int)} will be called when it finally loads. Note that if
     * this position stays outside of the cached item range (as defined by
     * {@link ViewCallback#extendRangeInto} method), then the callback will never be called for
     * this position.
     *
     * @param position Item position.
     *
     * @return The data item at the given position or <code>null</code> if it has not been loaded
     *         yet.
     */
    @Nullable
    public T getItem(int position) {
        if (position < 0 || position >= mItemCount) {
            throw new IndexOutOfBoundsException(position + " is not within 0 and " + mItemCount);
        }
        T item = mTileList.getItemAt(position);
        if (item == null && !isRefreshPending()) {
            mMissingPositions.put(position, 0);
        }
        return item;
    }

    /**
     * Returns the number of items in the data set.
     *
     * <p>
     * This is the number returned by a recent call to
     * {@link DataCallback#refreshData()}.
     *
     * @return Number of items.
     */
    public int getItemCount() {

View on GitHub (pinned to 45ab8f4308)

Solutions

  1. Ensure RecyclerView.Adapter.getItemCount() and AsyncListUtil.DataCallback.refreshData report the same total count, and call asyncListUtil.refresh() whenever the count changes.
  2. Guard the call site: if (position >= 0 && position < asyncListUtil.getItemCount()) before getItem.
  3. Update the adapter's itemCount atomically with the AsyncListUtil refresh to avoid stale positions.

Example fix

// before
String item = asyncListUtil.getItem(position); // may throw if stale

// after
if (position >= 0 && position < asyncListUtil.getItemCount()) {
    String item = asyncListUtil.getItem(position);
} else {
    item = null;
}
Defensive patterns

Strategy: validation

Validate before calling

// Bounds-check before getItem
public <T> T safeGet(AsyncListUtil<T> util, int position) {
    if (position < 0 || position >= util.getItemCount()) return null;
    return util.getItem(position);
}

Try / catch

try { return asyncListUtil.getItem(position); } catch (IndexOutOfBoundsException e) { Log.w(TAG, "stale position " + position); return null; }

Prevention

When it happens

Trigger: The RecyclerView adapter getItemCount() returns a larger value than the AsyncListUtil mItemCount (size mismatch between adapter and the AsyncListUtil); a position derived from a stale cursor after the data set changed; off-by-one when mapping a scroll position to an item index.

Common situations: Calling refresh() on the AsyncListUtil without also updating the adapter count; the adapter and DataCallback disagree on total count after a background refresh; rapid scroll past the end during a data swap.

Related errors


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