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
- Ensure the adapter has stable ids: setHasStableIds(true) must be applied before models are used (EpoxyAdapter does this when models have ids)
- Give every model a unique non-null id so stable ids are valid
- Use EpoxyController / EpoxyRecyclerView instead, which handle ids and diffing automatically
- 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
- Ensure setHasStableIds(true) is applied before models are shown
- Give every model a unique, stable id
- Prefer EpoxyController which enforces this automatically
- Skip enableDiffing() entirely if stable ids are not feasible
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
- Must have stable ids when saving view holder state
- 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…
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 enableView on GitHub (pinned to e45bd3a61f)