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

  1. Snapshot both lists before computing the diff so areItemsTheSame sees stable data.
  2. Ensure areItemsTheSame and areContentsTheSame are deterministic and consistent for the same (oldItem,newItem) pair.
  3. Make item identity (areItemsTheSame) stable via a unique id, not mutable fields.
  4. 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

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


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