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
- Ensure all model list mutations happen on the main/single UI thread
- Rebuild the model list (setModels) instead of mutating ModelState directly
- If reproducible, upgrade Epoxy — this can indicate a diffing bug
- 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
- Mutate the model list only on the main thread
- Never mutate ModelState or call pairWithSelf directly
- Prefer full setModels rebuilds over incremental internal diffing
- Upgrade Epoxy if reproducible with current versions
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
- Diffing is enabled. You should use notifyModelsChanged…
- Moving more than 1 item at a time is not supported. Number…
- Unknown type
- Two models have the same ID. ID's must be unique! Model at…
- Models must not be empty
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=" + positionView on GitHub (pinned to e45bd3a61f)