airbnb/epoxy · error · IllegalStateException

You must have stable ids to use diffing

Error message

You must have stable ids to use diffing

What it means

Diffing relies on RecyclerView stable IDs; enableDiffing() throws IllegalStateException when hasStableIds() is false. EpoxyAdapter sets hasStableIds true only when model ids are available, since the diff and payloads are keyed by id.

Solutions

  1. Ensure the adapter has stable ids: setHasStableIds(true) must be applied before models are used (EpoxyAdapter does this when models have ids)
  2. Give every model a unique non-null id so stable ids are valid
  3. Use EpoxyController / EpoxyRecyclerView instead, which handle ids and diffing automatically
  4. If stable ids are impossible, do not use enableDiffing(); manage notifications manually

Example fix

// before
class MyAdapter extends EpoxyAdapter {
  MyAdapter() { enableDiffing(); } // throws: no stable ids
}

// after
class MyAdapter extends EpoxyAdapter {
  MyAdapter() {
    setHasStableIds(true);
    enableDiffing();
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// verify stable ids before enabling diffing
if (!adapter.hasStableIds()) { adapter.setHasStableIds(true); }
enableDiffing();

Prevention

When it happens

Trigger: Enabling diffing on an EpoxyAdapter without stable ids enabled (setHasStableIds(true) not in effect / models lacking ids).

Common situations: Custom EpoxyAdapter subclasses that override id behavior or construct the adapter in a way that skips stable-id setup; migrating a plain RecyclerView.Adapter pattern without stable ids.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyAdapter.java:52

  }

  /**
   * Enables support for automatically notifying model changes via {@link #notifyModelsChanged()}.
   * If used, this should be called in the constructor, before any models are changed.
   *
   * @see #notifyModelsChanged()
   */
  protected void enableDiffing() {
    if (diffHelper != null) {
      throw new IllegalStateException("Diffing was already enabled");
    }

    if (!models.isEmpty()) {
      throw new IllegalStateException("You must enable diffing before modifying models");
    }

    if (!hasStableIds()) {
      throw new IllegalStateException("You must have stable ids to use diffing");
    }

    diffHelper = new DiffHelper(this, false);
  }

  @Override
  EpoxyModel<?> getModelForPosition(int position) {
    EpoxyModel<?> model = models.get(position);
    return model.isShown() ? model : hiddenModel;
  }

  /**
   * Intelligently notify item changes by comparing the current {@link #models} list against the
   * previous so you don't have to micromanage notification calls yourself. This may be
   * prohibitively slow for large model lists (in the hundreds), in which case consider doing
   * notification calls yourself. If you use this, all your view models must implement {@link
   * EpoxyModel#hashCode()} and {@link EpoxyModel#equals(Object)} to completely identify their
   * state, so that changes to a model's content can be detected. Before using this you must enable

View on GitHub (pinned to e45bd3a61f)