DrKLO/Telegram · critical · IndexOutOfBoundsException
Inconsistency detected. Invalid item position {position}(off
Error message
Inconsistency detected. Invalid item position {position}(offset:{offsetPosition}).state:{itemCount} What it means
Recycler.bindViewToPosition throws IndexOutOfBoundsException ('Inconsistency detected. Invalid item position') when mAdapterHelper.findPositionOffset(position) returns a value outside [0, mAdapter.getItemCount()). findPositionOffset translates a pre-layout position into an adapter position by replaying pending notify operations; an out-of-range result means the requested position does not map to a real adapter index after applying pending removes/inserts. As with error 49, this signals that the adapter's pending update events do not match the actual state of the backing data.
Source
Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/RecyclerView.java:6064
* and let the RecyclerView handle caching. This is a helper method for LayoutManager who
* wants to handle its own recycling logic.
* <p>
* Note that, {@link #getViewForPosition(int)} already binds the View to the position so
* you don't need to call this method unless you want to bind this View to another position.
*
* @param view The view to update.
* @param position The position of the item to bind to this View.
*/
public void bindViewToPosition(@NonNull View view, int position) {
ViewHolder holder = getChildViewHolderInt(view);
if (holder == null) {
throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot"
+ " pass arbitrary views to this method, they should be created by the "
+ "Adapter" + exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}
tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS);
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mInsetsDirty = true;View on GitHub (pinned to 45ab8f4308)
Solutions
- Guarantee notify ranges exactly match the backing-list delta — remove N items with notifyItemRangeRemoved(idx, N) and vice versa for inserts; prefer ListAdapter.submitList with a correct DiffUtil to avoid manual range math.
- In a custom LayoutManager, always read the adapter count from state.getItemCount() (the Recycler/State APIs), never cache counts across layout passes.
- Ensure all mutations and notify calls happen on the main thread, atomically (mutate then immediately notify).
- If corruption already occurred, call notifyDataSetChanged() to reset position bookkeeping before further updates.
Example fix
// before items.addAll(0, newBatch); notifyItemRangeInserted(0, newBatch.size() - 1); // off-by-one -> invalid offset // after int n = newBatch.size(); items.addAll(0, newBatch); notifyItemRangeInserted(0, n);
Defensive patterns
Strategy: validation
Validate before calling
int count = adapter.getItemCount();
if (position < 0 || position >= count) {
throw new IndexOutOfBoundsException("position " + position + " out of [0," + count + ")");
} Prevention
- Ensure every notify range exactly matches the backing-list size delta.
- Use ListAdapter.submitList to avoid manual range math.
- In custom LayoutManagers, read counts from State each layout, never cache.
When it happens
Trigger: A LayoutManager requesting a position that exceeds the current adapter count (e.g. after items were removed but the LM still references stale positions); notifyItemRangeRemoved/Inserted with a count that does not match the real list delta; requesting a position during pre-layout that AdapterHelper cannot resolve; concurrent data mutation invalidating position offsets.
Common situations: Custom LayoutManager holding stale position indices; inconsistent notify ranges; rapid add/remove sequences where notify math drifts; background-thread list mutation racing a layout pass.
Related errors
- Inconsistency detected. Invalid view holder adapter position
- Two different ViewHolders have the same change ID. This migh
- Two different ViewHolders have the same stable ID. Stable ID
- should not dispatch add or move for pre layout
- Moving more than 1 item is not supported yet
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/ef99e4c86886c342.
Report an issue: GitHub.