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

  1. Set a controller first with recyclerView.withModels { ... } or buildModelsWith
  2. If you used setModels, re-call setModels with fresh models instead of requestModelBuild
  3. 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

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


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)