airbnb/epoxy · error · IllegalStateException

Already paired.

Error message

Already paired.

What it means

ModelState.pairWithSelf creates a paired ModelState used during diffing to track moves of newly inserted items. It throws IllegalStateException if the model already has a pair, since re-pairing would corrupt the diff algorithm's invariants.

Solutions

  1. Ensure all model list mutations happen on the main/single UI thread
  2. Rebuild the model list (setModels) instead of mutating ModelState directly
  3. If reproducible, upgrade Epoxy — this can indicate a diffing bug
  4. Never call pairWithSelf from application code

Example fix

// before
new Thread(() -> controller.setModels(list)).start();
// after
runOnUiThread(() -> controller.setModels(list));
Defensive patterns

Strategy: validation

Validate before calling

synchronized (controller) { controller.setModels(newList); }

Try / catch

try { diffHelper.notifyModelStateChanges(); } catch (IllegalStateException e) { controller.requestModelBuild(); }

Prevention

When it happens

Trigger: pairWithSelf() invoked on a ModelState whose `pair` field is already non-null — a diff-internals invariant violation, typically from a corrupted or concurrently-modified model list during notifyModelStateChanges.

Common situations: Concurrent modification of the model list while the diff is running; internal Epoxy diff bugs; calling internal ModelState APIs from app code.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13). Data as JSON: /api/errors/aee5b8af6d7478d3. Report an issue: GitHub.

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelState.java:50

    state.id = model.id();
    state.position = position;

    if (immutableModel) {
      state.model = model;
    } else {
      state.hashCode = model.hashCode();
    }

    return state;
  }

  /**
   * Used for an item inserted into the new list when we need to track moves that effect the
   * inserted item in the old list.
   */
  void pairWithSelf() {
    if (pair != null) {
      throw new IllegalStateException("Already paired.");
    }

    pair = new ModelState();
    pair.lastMoveOp = 0;
    pair.id = id;
    pair.position = position;
    pair.hashCode = hashCode;
    pair.pair = this;
    pair.model = model;
  }

  @Override
  public String toString() {
    return "ModelState{"
        + "id=" + id
        + ", model=" + model
        + ", hashCode=" + hashCode
        + ", position=" + position

View on GitHub (pinned to e45bd3a61f)