airbnb/epoxy · error · IllegalEpoxyUsage

Cannot call `requestDelayedModelBuild` from inside…

Error message

Cannot call `requestDelayedModelBuild` from inside `buildModels`

What it means

requestDelayedModelBuild(delayMs) schedules a debounced buildModels() pass; invoking it while buildModels() is already running would queue a build inside the build being executed, so an IllegalEpoxyUsage is thrown. Like requestModelBuild, it must be called outside model building.

Solutions

  1. Call requestDelayedModelBuild() only outside buildModels (e.g. after data updates)
  2. Replace in-build scheduling with addModelBuildListener + post-build request
  3. If using moveModel, ensure it is invoked outside the buildModels pass

Example fix

// before
@Override
protected void buildModels() {
  requestDelayedModelBuild(100); // crash
  ...
}
// after
void onDataChanged() {
  requestDelayedModelBuild(100);
}
@Override
protected void buildModels() { ... }
Defensive patterns

Strategy: try-catch

Validate before calling

if (!controller.isBuildingModels()) { controller.requestDelayedModelBuild(delayMs); }

Try / catch

try { controller.requestDelayedModelBuild(100); } catch (IllegalEpoxyUsage e) { /* defer until after current build */ }

Prevention

When it happens

Trigger: Calling requestDelayedModelBuild() (directly, or via moveModel) from within buildModels().

Common situations: Scheduling a delayed refresh from inside buildModels; calling controller.moveModel(...) during building instead of outside it; chaining a delayed build from logic invoked by buildModels.

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

Appendix: source

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

   * <p>
   * Using this to delay a model update may be helpful in cases where user input is causing many
   * rapid changes in the models, such as typing. In that case, the view is already updated on
   * screen and constantly rebuilding models is potentially slow and unnecessary. The downside to
   * delaying the model build too long is that models will not be in sync with the data or view, and
   * scrolling the view offscreen and back onscreen will cause the model to bind old data.
   * <p>
   * If a previous request is still pending it will be removed in favor of this new delay
   * <p>
   * Any call to {@link #requestModelBuild()} will override a delayed request.
   * <p>
   * In most cases you should use {@link #requestModelBuild()} instead of this.
   *
   * @param delayMs The time in milliseconds to delay the model build by. Should be greater than or
   *                equal to 0. A value of 0 is equivalent to calling {@link #requestModelBuild()}
   */
  public synchronized void requestDelayedModelBuild(int delayMs) {
    if (isBuildingModels()) {
      throw new IllegalEpoxyUsage(
          "Cannot call `requestDelayedModelBuild` from inside `buildModels`");
    }

    if (requestedModelBuildType == RequestedModelBuildType.DELAYED) {
      cancelPendingModelBuild();
    } else if (requestedModelBuildType == RequestedModelBuildType.NEXT_FRAME) {
      return;
    }

    requestedModelBuildType =
        delayMs == 0 ? RequestedModelBuildType.NEXT_FRAME : RequestedModelBuildType.DELAYED;

    modelBuildHandler.postDelayed(buildModelsRunnable, delayMs);
  }

  /**
   * Cancels a pending call to {@link #buildModels()} if one has been queued by {@link
   * #requestModelBuild()}.

View on GitHub (pinned to e45bd3a61f)