airbnb/epoxy · error · IllegalStateException

You must enable diffing before notifying models changed

Error message

You must enable diffing before notifying models changed

What it means

EpoxyAdapter throws this IllegalStateException when notifyModelsChanged() is called while diffing has never been enabled (diffHelper is null). Diffing is an opt-in feature; without it there is no DiffHelper to compute model changes, so notifying would be incorrect. The library forces you to call enableDiffing() first.

Solutions

  1. Call enableDiffing() in your adapter's constructor before any model notifications occur
  2. Verify your adapter setup path actually runs enableDiffing() (check that it isn't gated behind a flag or skipped branch)
  3. Prefer migrating to EpoxyController/EpoxyRecyclerView with controllers, which manage diffing automatically

Example fix

// before
class MyAdapter extends EpoxyAdapter {
  MyAdapter() { /* no enableDiffing */ }
  void update() { notifyModelsChanged(); }
}
// after
class MyAdapter extends EpoxyAdapter {
  MyAdapter() { enableDiffing(); }
  void update() { notifyModelsChanged(); }
}
Defensive patterns

Strategy: validation

Validate before calling

if (adapter.getModelPosition(model) >= 0 || adapter.getClass().isAnnotationPresent(DiffingEnabled.class)) { /* ensure enableDiffing() called in constructor */ }

Try / catch

try { adapter.notifyModelsChanged(); } catch (IllegalStateException e) { adapter.enableDiffing(); adapter.notifyModelsChanged(); }

Prevention

When it happens

Trigger: Calling the protected notifyModelsChanged() (directly or via helpers like insertModelBefore/insertModelAfter/getAllModelsAfter paths that rely on notifications) on an EpoxyAdapter subclass that never called enableDiffing().

Common situations: Subclassing EpoxyAdapter manually (instead of using EpoxyController) and calling model-mutation helpers that expect diffing; copying adapter code that assumes diffing was enabled in a base class or setup method that was skipped.

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/96d84c50922a6616. Report an issue: GitHub.

Appendix: source

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

    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
   * it with {@link #enableDiffing()}, since keeping track of the model state adds extra computation
   * time to all other data change notifications.
   *
   * @see #enableDiffing()
   */

  protected void notifyModelsChanged() {
    if (diffHelper == null) {
      throw new IllegalStateException("You must enable diffing before notifying models changed");
    }

    diffHelper.notifyModelChanges();
  }

  /**
   * Notify that the given model has had its data changed. It should only be called if the model
   * retained the same position.
   */
  protected void notifyModelChanged(EpoxyModel<?> model) {
    notifyModelChanged(model, null);
  }

  /**
   * Notify that the given model has had its data changed. It should only be called if the model
   * retained the same position.
   */
  protected void notifyModelChanged(EpoxyModel<?> model, @Nullable Object payload) {

View on GitHub (pinned to e45bd3a61f)