airbnb/epoxy · critical · IllegalStateException

Two models have the same ID. ID's must be unique! Model at…

Error message

Two models have the same ID. ID's must be unique! Model at position {position}: {model} Model at position {previousPosition}: {previousModel}

What it means

createStateForPosition builds a ModelState keyed by model id; if another model already occupies that id in currentStateMap, it throws IllegalStateException listing both positions and models. Epoxy requires globally unique model ids within a set of models because diffing and stable-id RecyclerView behavior key off them.

Solutions

  1. Assign a unique id to every model (e.g. .id(position) or a stable business key)
  2. Check for accidental duplicate add() of the same model instance
  3. If a model legitimately appears twice, give each occurrence a distinct id or use EpoxyModel with id including a sub-identifier
  4. Add a debug assert before setModels to detect duplicate ids early (EpoxyController.validateModelBuilding does this in controllers)

Example fix

// before
for (Item item : items) {
  add(new ItemModel_().id(1).item(item)); // duplicate id
}

// after
for (Item item : items) {
  add(new ItemModel_().id(item.getId()).item(item));
}
Defensive patterns

Strategy: validation

Validate before calling

// assert unique ids before building models
Set<Long> seen = new HashSet<>();
for (EpoxyModel<?> m : models) {
  if (!seen.add(m.id())) throw new IllegalStateException("Duplicate model id: " + m.id());
}

Prevention

When it happens

Trigger: Adding two models with the same id() to the adapter's model list — e.g. duplicate add(), a loop reusing one model instance/id, or forgetting to set a unique id on programmatically built models — then building state via prepareStateForDiff or observing an insert.

Common situations: Models created in a loop without setting distinct ids; accidentally adding the same model instance twice; two view types sharing a default/auto id; switching to diffing from an adapter that never enforced unique ids.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/DiffHelper.java:241

    int modelCount = adapter.getCurrentModels().size();
    currentStateList.ensureCapacity(modelCount);

    for (int i = 0; i < modelCount; i++) {
      currentStateList.add(createStateForPosition(i));
    }
  }

  private ModelState createStateForPosition(int position) {
    EpoxyModel<?> model = adapter.getCurrentModels().get(position);
    model.addedToAdapter = true;
    ModelState state = ModelState.build(model, position, immutableModels);

    ModelState previousValue = currentStateMap.put(state.id, state);
    if (previousValue != null) {
      int previousPosition = previousValue.position;
      EpoxyModel<?> previousModel = adapter.getCurrentModels().get(previousPosition);
      throw new IllegalStateException("Two models have the same ID. ID's must be unique!"
          + " Model at position " + position + ": " + model
          + " Model at position " + previousPosition + ": " + previousModel);
    }

    return state;
  }

  /**
   * Find all removal operations and add them to the result list. The general strategy here is to
   * walk through the {@link #oldStateList} and check for items that don't exist in the new list.
   * Walking through it in order makes it easy to batch adjacent removals.
   */
  private void collectRemovals(UpdateOpHelper helper) {
    for (ModelState state : oldStateList) {
      // Update the position of the item to take into account previous removals,
      // so that future operations will reference the correct position
      state.position -= helper.getNumRemovals();

View on GitHub (pinned to e45bd3a61f)