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
- Move the requestModelBuild() call outside buildModels (e.g. in the data-change handler that calls setData)
- Defer the request until after building completes (post to the main handler or use addModelBuildListener then request)
- 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
- Never call requestModelBuild from buildModels or code it invokes; use setData/onDataChanged entry points
- Use addModelBuildFinishedListener to schedule follow-up builds after completion
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
- Cannot call `requestDelayedModelBuild` from inside…
- Can only call this when inside the `buildModels` method
- Cannot call this from inside `buildModels`
- You cannot hide a model in an EpoxyController. Use `addIf`…
- Notifications already paused
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)