airbnb/epoxy · error · IllegalStateException

You cannot call `requestModelBuild` directly. Call…

Error message

You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a model refresh with new data.

What it means

Typed2EpoxyController is a data-driven controller: you feed it data via `setData(data1, data2)`, and it manages when model builds run. `requestModelBuild()` is final and throws `IllegalStateException` unless `allowModelBuildRequests` is true (which is only true briefly inside `setData`). This prevents callers from triggering a rebuild with stale internal data instead of providing fresh data.

Solutions

  1. Call `controller.setData(newData1, newData2)` with the updated data instead of `requestModelBuild()`.
  2. Keep a copy of the current data in your screen/fragment, mutate it, and pass the full new pair back through `setData`.
  3. If you need a delayed rebuild, pass new data via `setData`; the controller schedules the build itself.
  4. Refactor the subclass's `buildModels(data1, data2)` to derive everything from the data parameters rather than mutable external state.

Example fix

// before
items.add(newItem);
controller.requestModelBuild(); // IllegalStateException

// after
items.add(newItem);
controller.setData(items, otherData);
Defensive patterns

Strategy: validation

Validate before calling

// Typed2EpoxyController: refresh only through setData
// if (controller is Typed2EpoxyController<*, *>) controller.setData(d1, d2) else controller.requestModelBuild()

Prevention

When it happens

Trigger: Calling `controller.requestModelBuild()` from application code at any point other than indirectly through `setData`. For example, calling it after mutating an object that was previously passed to `setData`, hoping the controller will pick up the change.

Common situations: Porting code from base `EpoxyController` (where `requestModelBuild()` is the normal API) to a Typed controller without switching to `setData`; calling `requestModelBuild()` in a callback/listener after data changed outside the controller; copy-pasting refresh logic between typed and untyped controllers.

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

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/Typed2EpoxyController.java:47

    super(modelBuildingHandler, diffingHandler);
  }

  /**
   * Call this with the latest data when you want models to be rebuilt. The data will be passed on
   * to {@link #buildModels(Object, Object)}
   */
  public void setData(T data1, U data2) {
    this.data1 = data1;
    this.data2 = data2;
    allowModelBuildRequests = true;
    requestModelBuild();
    allowModelBuildRequests = false;
  }

  @Override
  public final void requestModelBuild() {
    if (!allowModelBuildRequests) {
      throw new IllegalStateException(
          "You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a "
              + "model refresh with new data.");
    }
    super.requestModelBuild();
  }

  @Override
  public void moveModel(int fromPosition, int toPosition) {
    allowModelBuildRequests = true;
    super.moveModel(fromPosition, toPosition);
    allowModelBuildRequests = false;
  }

  @Override
  public void requestDelayedModelBuild(int delayMs) {
    if (!allowModelBuildRequests) {
      throw new IllegalStateException(
          "You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a "

View on GitHub (pinned to e45bd3a61f)