airbnb/epoxy · error · IllegalStateException

You cannot notify item changes directly. Call…

Error message

You cannot notify item changes directly. Call `requestModelBuild` instead.

What it means

Epoxy's controller blocks direct adapter notify calls: NotifyBlocker.onChanged throws IllegalStateException because callers must rebuild models via requestModelBuild instead of notifying RecyclerView changes manually. The blocked methods (onChanged and the range variants) are the ones RecyclerView's Adapter exposes for manual change notifications.

Solutions

  1. Delete all adapter.notifyXxx calls and let the controller rebuild models
  2. Call controller.requestModelBuild() after changing your data
  3. Ensure adapter data flows through setModels / buildModels only

Example fix

// before
adapter.notifyDataSetChanged();
// after
controller.requestModelBuild();
Defensive patterns

Strategy: fallback

Validate before calling

if (adapter instanceof NotifyBlocker) { controller.requestModelBuild(); } else { adapter.notifyDataSetChanged(); }

Try / catch

try { adapter.notifyDataSetChanged(); } catch (IllegalStateException e) { controller.requestModelBuild(); }

Prevention

When it happens

Trigger: Calling adapter.notifyDataSetChanged() / notifyItemChanged/Inserted/Removed/Moved on the adapter managed by an Epoxy controller (any notify variant while changesAllowed == false, which the controller sets).

Common situations: Porting standard RecyclerView Adapter code to Epoxy without removing manual notify calls; helper libraries or animations that call notifyDataSetChanged directly.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/NotifyBlocker.java:27

 * <p>
 * This observer throws upon any changes done outside of diffing.
 */
class NotifyBlocker extends AdapterDataObserver {

  private boolean changesAllowed;

  void allowChanges() {
    changesAllowed = true;
  }

  void blockChanges() {
    changesAllowed = false;
  }

  @Override
  public void onChanged() {
    if (!changesAllowed) {
      throw new IllegalStateException(
          "You cannot notify item changes directly. Call `requestModelBuild` instead.");
    }
  }

  @Override
  public void onItemRangeChanged(int positionStart, int itemCount) {
    onChanged();
  }

  @Override
  public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    onChanged();
  }

  @Override
  public void onItemRangeInserted(int positionStart, int itemCount) {
    onChanged();
  }

View on GitHub (pinned to e45bd3a61f)