DrKLO/Telegram · critical · IllegalStateException
unknown flag for pos {} {}
Error message
unknown flag for pos {} {} What it means
While dispatching ADDITIONS during DiffResult.dispatchUpdatesTo, each position has a status flag encoding its fate (added, moved, moved-and-changed, ignored). The switch covers the known FLAG_* constants; the default fires when the status word has an unrecognized combination — which should never happen for a correctly computed DiffResult. Reaching it means the internal mOldItemStatuses/mNewItemStatuses arrays were populated incorrectly, almost always because the DiffUtil.Callback returned inconsistent results (the same root cause as error 8: data changed during the diff).
Source
Thrown at TMessagesProj/src/main/java/androidx/recyclerview/widget/DiffUtil.java:904
case FLAG_MOVED_CHANGED:
case FLAG_MOVED_NOT_CHANGED:
final int pos = mNewItemStatuses[globalIndex + i] >> FLAG_OFFSET;
final PostponedUpdate update = removePostponedUpdate(postponedUpdates, pos,
true);
// the item was moved from that position
//noinspection ConstantConditions
updateCallback.onMoved(update.currentPos, start);
if (status == FLAG_MOVED_CHANGED) {
// also dispatch a change
updateCallback.onChanged(start, 1,
mCallback.getChangePayload(pos, globalIndex + i));
}
break;
case FLAG_IGNORE: // ignoring this
postponedUpdates.add(new PostponedUpdate(globalIndex + i, start, false));
break;
default:
throw new IllegalStateException(
"unknown flag for pos " + (globalIndex + i) + " " + Long
.toBinaryString(status));
}
}
}
private void dispatchRemovals(List<PostponedUpdate> postponedUpdates,
ListUpdateCallback updateCallback, int start, int count, int globalIndex) {
if (!mDetectMoves) {
updateCallback.onRemoved(start, count);
return;
}
for (int i = count - 1; i >= 0; i--) {
final int status = mOldItemStatuses[globalIndex + i] & FLAG_MASK;
switch (status) {
case 0: // real removal
updateCallback.onRemoved(start + i, 1);
for (PostponedUpdate update : postponedUpdates) {View on GitHub (pinned to 45ab8f4308)
Solutions
- Snapshot both lists before computing the diff so areItemsTheSame sees stable data.
- Ensure areItemsTheSame and areContentsTheSame are deterministic and consistent for the same (oldItem,newItem) pair.
- Make item identity (areItemsTheSame) stable via a unique id, not mutable fields.
- Avoid getChangePayload side effects that touch the source list.
Defensive patterns
Strategy: validation
Validate before calling
// Deterministic Callback over snapshots
List<Item> oldSnap, newSnap; // frozen
new DiffUtil.Callback() {
public boolean areItemsTheSame(int o, int n) {
return oldSnap.get(o).id == newSnap.get(n).id; // stable id
}
public boolean areContentsTheSame(int o, int n) {
return Objects.equals(oldSnap.get(o), newSnap.get(n));
}
}; Prevention
- Base areItemsTheSame on a stable unique id, never mutable fields.
- Snapshot both lists so Callback answers are consistent across calls.
- Keep getChangePayload pure and side-effect free.
When it happens
Trigger: DiffUtil.Callback.areItemsTheSame/areContentsTheSame return contradictory answers across calls (e.g. item hashes differ run to run); the source list mutated during calculateDiff; getChangePayload returns inconsistent data causing flag mis-encoding; a custom Callback that returns true for areItemsTheSame but true for areContentsTheSame inconsistently across the same pair.
Common situations: Items whose hashCode/equality changes between calls (mutable fields mutated mid-diff); DB-backed list where the query result set shifted during the diff; using getOldListSize/getNewListSize that read live collections.
Related errors
- DiffUtil hit an unexpected case while trying to calculate th
- Index out of bounds - passed position = {}, old list size =
- Index out of bounds - passed position = {}, new list size =
- should not dispatch add or move for pre layout
- op should be remove or update.{}
AI-assisted analysis of DrKLO/Telegram@45ab8f4308 (2026-08-14).
Data as JSON: /api/errors/33a7f12b2b913fcd.
Report an issue: GitHub.