airbnb/epoxy · error · IllegalEpoxyUsage

Cannot call this from inside `buildModels`

Error message

Cannot call this from inside `buildModels`

What it means

assertNotBuildingModels() enforces that controller APIs which mutate or query the already-built model list (setDebugLoggingEnabled, moveModel, notifyModelChanged) are not called during an in-progress buildModels() pass, since the built list is mid-flight. Violating this throws IllegalEpoxyUsage.

Solutions

  1. Call moveModel/notifyModelChanged only after buildModels completes (post to main thread if needed)
  2. Replace in-build moveModel with ordering the models correctly during building
  3. Enable setDebugLoggingEnabled once at controller setup, not during builds

Example fix

// before
@Override
protected void buildModels() {
  moveModel(model, 0); // crash
  ...
}
// after
void onReady() {
  moveModel(model, 0);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!controller.isBuildingModels()) { controller.moveModel(model, toIndex); }

Type guard

boolean canMutateBuiltList(EpoxyController c) { return !c.isBuildingModels(); }

Try / catch

try { controller.notifyModelChanged(model); } catch (IllegalEpoxyUsage e) { /* post until after build */ }

Prevention

When it happens

Trigger: Calling moveModel, notifyModelChanged, or setDebugLoggingEnabled from inside buildModels() (directly or from code invoked by buildModels).

Common situations: Reordering or refreshing models inside buildModels in response to data; calling notifyModelChanged from a model's click handler that executes during building; enabling debug logging in a build-time callback.

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

Appendix: source

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

   * <p>
   * This is different from the number of models currently on the adapter, since models on the
   * adapter are not updated until after models are finished being built. To access current adapter
   * count call {@link #getAdapter()} and {@link EpoxyControllerAdapter#getItemCount()}
   */
  protected int getModelCountBuiltSoFar() {
    assertIsBuildingModels();
    return modelsBeingBuilt.size();
  }

  private void assertIsBuildingModels() {
    if (!isBuildingModels()) {
      throw new IllegalEpoxyUsage("Can only call this when inside the `buildModels` method");
    }
  }

  private void assertNotBuildingModels() {
    if (isBuildingModels()) {
      throw new IllegalEpoxyUsage("Cannot call this from inside `buildModels`");
    }
  }

  /**
   * Add the model to this controller. Can only be called from inside {@link
   * EpoxyController#buildModels()}.
   */
  public void add(@NonNull EpoxyModel<?> model) {
    model.addTo(this);
  }

  /**
   * Add the models to this controller. Can only be called from inside {@link
   * EpoxyController#buildModels()}.
   */
  protected void add(@NonNull EpoxyModel<?>... modelsToAdd) {
    modelsBeingBuilt.ensureCapacity(modelsBeingBuilt.size() + modelsToAdd.length);

View on GitHub (pinned to e45bd3a61f)