airbnb/epoxy · error · IllegalStateException
You cannot call `buildModels` directly. Call `setData`…
Error message
You cannot call `buildModels` directly. Call `setData` instead to trigger a model refresh with new data.
What it means
Typed2EpoxyController's `buildModels()` is final and only delegates to the abstract `buildModels(data1, data2)` when `isBuildingModels()` is true — i.e. during a build pass the controller itself started. Otherwise it throws `IllegalStateException`, catching any attempt to invoke the model-building phase directly. Model building must be initiated by `setData`, not by callers.
Solutions
- Call `controller.setData(data1, data2)` to trigger `buildModels(data1, data2)` through the proper lifecycle.
- In tests, use `setData` (or Epoxy's test helpers / `EpoxyController#buildModels` test utilities) rather than calling the final `buildModels()`.
- Never call `buildModels()` from constructors, init blocks, or other controllers.
- If you need models without an adapter, extract your model-building logic into a plain function returning a list and have `buildModels` add from it.
Example fix
// before List<EpoxyModel<?>> models = new ArrayList<>(); controller.buildModels(); // IllegalStateException // after controller.setData(data1, data2); // runs buildModels(data1, data2) safely
Defensive patterns
Strategy: validation
Validate before calling
// Wrong: controller.buildModels(); // Right: controller.setData(data1, data2); // safely runs buildModels(data1, data2)
Prevention
- Never invoke `buildModels` — it's a protected lifecycle override, not a public refresh API.
- In unit tests, assert on models by driving `setData` or extracting pure builder functions.
- Don't call buildModels from constructors or other controllers.
When it happens
Trigger: Calling `controller.buildModels()` (or the two-arg `buildModels(data1, data2)` override from outside) in app code, tests, or another controller to force models to be generated outside a legitimate build pass.
Common situations: Unit tests invoking `buildModels()` directly to assert on produced models; a developer calling `buildModels()` after updating data instead of calling `setData`; a subclass constructor or init block calling `buildModels()` eagerly to prime the adapter.
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
- You cannot call `requestModelBuild` directly. Call…
- You cannot call `requestModelBuild` directly. Call…
- You cannot call `buildModels` directly. Call `setData`…
- You cannot call `requestModelBuild` directly. Call…
- Can only call this when inside the `buildModels` method
AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13).
Data as JSON: /api/errors/a8700ea74e75d31f.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/Typed2EpoxyController.java:74
allowModelBuildRequests = true;
super.moveModel(fromPosition, toPosition);
allowModelBuildRequests = false;
}
@Override
public void requestDelayedModelBuild(int delayMs) {
if (!allowModelBuildRequests) {
throw new IllegalStateException(
"You cannot call `requestModelBuild` directly. Call `setData` instead to trigger a "
+ "model refresh with new data.");
}
super.requestDelayedModelBuild(delayMs);
}
@Override
protected final void buildModels() {
if (!isBuildingModels()) {
throw new IllegalStateException(
"You cannot call `buildModels` directly. Call `setData` instead to trigger a model "
+ "refresh with new data.");
}
buildModels(data1, data2);
}
protected abstract void buildModels(T data1, U data2);
}
View on GitHub (pinned to e45bd3a61f)