airbnb/epoxy · error · IllegalEpoxyUsage

Cannot call `requestModelBuild` from inside `buildModels`

Error message

Cannot call `requestModelBuild` from inside `buildModels`

What it means

EpoxyController.requestModelBuild() schedules a fresh buildModels() pass, but calling it from inside buildModels() would recursively re-enter model building, so an IllegalEpoxyUsage is thrown. Model building must complete before a new build can be requested.

Solutions

  1. Move the requestModelBuild() call outside buildModels (e.g. in the data-change handler that calls setData)
  2. Defer the request until after building completes (post to the main handler or use addModelBuildListener then request)
  3. If shared code updates data and requests a build, split it so the request happens only when not building (check isBuildingModels())

Example fix

// before
@Override
protected void buildModels() {
  requestModelBuild(); // crash
  ...
}
// after
void onDataChanged(Data d) {
  setData(d);
  requestModelBuild();
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!controller.isBuildingModels()) { controller.requestModelBuild(); }

Try / catch

try { controller.requestModelBuild(); } catch (IllegalEpoxyUsage e) { controller.post { -> requestModelBuild after build completes } }

Prevention

When it happens

Trigger: Calling requestModelBuild() (or APIs that trigger it) inside the buildModels() method of a controller — e.g. in response to observing data, or in a model-build listener invoked during building.

Common situations: Calling controller.requestModelBuild() from a data-binding callback fired while models are being built; requesting a rebuild inside an interceptor or in code called from buildModels via a shared update() method.

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

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyController.java:155

    int DELAYED = 2;
  }

  /**
   * Call this to request a model update. The controller will schedule a call to {@link
   * #buildModels()} so that models can be rebuilt for the current data. Once a build is requested
   * all subsequent requests are ignored until the model build runs. Therefore, the calling code
   * need not worry about calling this multiple times in a row.
   * <p>
   * The exception is that the first time this is called on a new instance of {@link
   * EpoxyController} it is run synchronously. This allows state to be restored and the initial view
   * to be draw quicker.
   * <p>
   * If you would like to be alerted when models have finished building use
   * {@link #addModelBuildListener(OnModelBuildFinishedListener)}
   */
  public void requestModelBuild() {
    if (isBuildingModels()) {
      throw new IllegalEpoxyUsage("Cannot call `requestModelBuild` from inside `buildModels`");
    }

    // If it is the first time building models then we do it right away, otherwise we post the call.
    // We want to do it right away the first time so that scroll position can be restored correctly,
    // shared element transitions aren't delayed, and content is shown asap. We post later calls
    // so that they are debounced, and so any updates to data can be completely finished before
    // the models are built.
    if (hasBuiltModelsEver) {
      requestDelayedModelBuild(0);
    } else {
      buildModelsRunnable.run();
    }
  }

  /**
   * Whether an update to models is currently pending. This can either be because
   * {@link #requestModelBuild()} was called, or because models are currently being built or diff
   * on a background thread.

View on GitHub (pinned to e45bd3a61f)