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
- Call moveModel/notifyModelChanged only after buildModels completes (post to main thread if needed)
- Replace in-build moveModel with ordering the models correctly during building
- 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
- Issue moveModel/notifyModelChanged from post-build callbacks or main-thread posts
- Configure debug logging at controller initialization time only
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
- Can only call this when inside the `buildModels` method
- You cannot call `buildModels` directly. Call `setModels`…
- Cannot call `requestModelBuild` from inside `buildModels`
- Cannot call `requestDelayedModelBuild` from inside…
- You cannot hide a model in an EpoxyController. Use `addIf`…
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)