airbnb/epoxy · error · IllegalEpoxyUsage
You cannot call `buildModels` directly. Call `setModels`…
Error message
You cannot call `buildModels` directly. Call `setModels` instead.
What it means
SimpleEpoxyController declares a final `buildModels()` that only forwards to `add(currentModels)` when a model build is genuinely in progress (guarded by `isBuildingModels()`). Throwing `IllegalEpoxyUsage` here catches subclasses that try to invoke `buildModels()` directly, bypassing the build lifecycle. The library requires model rebuilds to go through `setModels`/`requestModelBuild` so diffing, threading, and interruption are handled correctly. Calling it directly would build models outside the controller's state machine and corrupt adapter state.
Solutions
- Replace the direct `buildModels()` call with `setModels(<your model list>)` if you already have models built.
- If you want the controller to rebuild its models, call `requestModelBuild()` instead.
- In tests, use `SimpleEpoxyController#setModels` or construct the models you need and set them, rather than invoking the protected `buildModels()`.
- Ensure your subclass's `buildModels()` override only builds and adds models, and never calls itself or sibling instances' `buildModels()`.
Example fix
// before controller.buildModels(); // after controller.setModels(myModels); // or, to rebuild from the controller's own buildModels(): controller.requestModelBuild();
Defensive patterns
Strategy: validation
Validate before calling
// Never call buildModels() from app code.
// Refresh via the public API:
if (models != null) {
controller.setModels(models); // or controller.requestModelBuild()
} Prevention
- Treat `buildModels()` as a protected lifecycle hook you override, never call.
- Use `setModels` when you have models, `requestModelBuild` when the controller should rebuild.
- In tests, drive the controller with `setModels`/`setData` rather than the build hook.
When it happens
Trigger: Calling `controller.buildModels()` directly from application code (e.g. in an activity, test, or after data changes) instead of `controller.setModels(...)` or `controller.requestModelBuild()`. It also fires if `buildModels()` is invoked while `isBuildingModels()` is false, i.e. outside an in-flight build pass.
Common situations: A subclass overrides `buildModels()` and a test tries to call it directly to force a build; a developer migrates from manual RecyclerView adapter code and calls `buildModels()` after updating data; someone copies controller internals into a helper that calls `buildModels()` to refresh the list.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Can only call this when inside the `buildModels` method
- Cannot call this from inside `buildModels`
- 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/3ccf93efca25949e.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/SimpleEpoxyController.java:36
currentModels = models;
insideSetModels = true;
requestModelBuild();
insideSetModels = false;
}
@Override
public final void requestModelBuild() {
if (!insideSetModels) {
throw new IllegalEpoxyUsage(
"You cannot call `requestModelBuild` directly. Call `setModels` instead.");
}
super.requestModelBuild();
}
@Override
protected final void buildModels() {
if (!isBuildingModels()) {
throw new IllegalEpoxyUsage(
"You cannot call `buildModels` directly. Call `setModels` instead.");
}
add(currentModels);
}
}
View on GitHub (pinned to e45bd3a61f)