airbnb/epoxy · error · IllegalStateException
A controller must be set before requesting a model build.
Error message
A controller must be set before requesting a model build.
What it means
EpoxyRecyclerView.requestModelBuild() rebuilds models via the controller previously set with withModels/buildModelsWith. If no controller was ever set, it throws this IllegalStateException instead of failing silently.
Solutions
- Set a controller first with recyclerView.withModels { ... } or buildModelsWith
- If you used setModels, re-call setModels with fresh models instead of requestModelBuild
- For TypedEpoxyController, call setData on the controller directly
Example fix
// before
recyclerView.requestModelBuild(); // no controller set
// after
recyclerView.withModels {
add(model)
}
// or, if using setModels:
recyclerView.setModels(buildModelList()); Defensive patterns
Strategy: validation
Validate before calling
if (recyclerView.epoxyController != null) recyclerView.requestModelBuild() else setModelsOrWithData()
Type guard
val canRequestBuild: Boolean get() = recyclerView.epoxyController != null && recyclerView.epoxyController !is SimpleEpoxyController
Try / catch
try { recyclerView.requestModelBuild() } catch (e: IllegalStateException) { recyclerView.withModels { /* rebuild */ } } Prevention
- Always call withModels/buildModelsWith before requestModelBuild
- Check epoxyController nullability before refreshing
- For setModels flows, re-call setModels instead
When it happens
Trigger: Calling recyclerView.requestModelBuild() when epoxyController is null — i.e., models were never provided via withModels/buildModelsWith, or you used setModels directly.
Common situations: Calling requestModelBuild on a RecyclerView where models were supplied via setModels; calling before withModels has run; using it with TypedEpoxyController flows that never attach through withModels.
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
- You cannot call `buildModels` directly. Call `setData`…
- You cannot call `requestModelBuild` directly. Call…
- You cannot call `buildModels` directly. Call `setData`…
- Models were set with #setModels, they can not be rebuilt.
- You must enable diffing before notifying models changed
AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13).
Data as JSON: /api/errors/e7269ca8eb77b462.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyRecyclerView.kt:535
/**
* Analagous to [EpoxyController.buildModels]. You should create new model instances and
* add them to the given controller. [AutoModel] cannot be used with models added this
* way.
*/
fun buildModels(controller: EpoxyController)
}
/**
* Request that the currently set EpoxyController has its models rebuilt. You can use this to
* avoid saving your controller as a field.
*
* You cannot use this if your controller is a [TypedEpoxyController] or if you set
* models via [setModels]. In that case you must set data directly on the
* controller or set models again.
*/
fun requestModelBuild() {
if (epoxyController == null) {
throw IllegalStateException("A controller must be set before requesting a model build.")
}
if (epoxyController is SimpleEpoxyController) {
throw IllegalStateException("Models were set with #setModels, they can not be rebuilt.")
}
epoxyController!!.requestModelBuild()
}
/**
* Clear the currently set EpoxyController or Adapter as well as any models that are displayed.
*
* Any pending requests to the EpoxyController to build models are canceled.
*
* Any existing child views are recycled to the view pool.
*/
open fun clear() {
// The controller is cleared so the next time models are set we can create a fresh one.View on GitHub (pinned to e45bd3a61f)