airbnb/epoxy · error · IllegalStateException

Insufficient view stubs for EpoxyModelGroup. $modelCount…

Error message

Insufficient view stubs for EpoxyModelGroup. $modelCount models were provided but only ${stubs.size} view stubs exist.

What it means

An EpoxyModelGroup needs at least one ViewStub per model so each model can be bound into its own stub. When the holder is in stub mode (usingStubs()) and the number of stubs in the layout is less than the number of models in the group, bindGroupIfNeeded throws this IllegalStateException because some models would have no view to bind to.

Solutions

  1. Add ViewStub entries to the group layout so stub count >= model count.
  2. Reduce the number of models in the EpoxyModelGroup to match the available stubs.
  3. Build the layout dynamically from the model list so stub count always tracks models.size().
  4. Check that you are not accidentally binding the same group layout to a larger model list after a refactor.

Example fix

// before
EpoxyModelGroup(models = listOf(modelA, modelB, modelC), layoutRes = R.layout.group_two_stubs)

// after: add a third ViewStub to group layout, or
EpoxyModelGroup(models = listOf(modelA, modelB), layoutRes = R.layout.group_two_stubs)
Defensive patterns

Strategy: validation

Validate before calling

fun validateGroupLayout(modelCount: Int, stubLayoutRes: Int, context: Context) {
  val stubs = countStubs(LayoutInflater.from(context).inflate(stubLayoutRes, null))
  require(stubs >= modelCount) { "Need $modelCount stubs, layout has $stubs" }
}

Prevention

When it happens

Trigger: Creating an EpoxyModelGroup with N models while its layout contains fewer than N ViewStub children (e.g. 3 models, 2 stubs).

Common situations: Adding models to a group without updating the group's layout to add matching stubs; reusing one group layout for differently sized groups; dynamic model-count changes at runtime not mirrored in the layout.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13). Data as JSON: /api/errors/25d8857e9315dd71. Report an issue: GitHub.

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelGroupHolder.kt:111

        if (previouslyBoundGroup === group) {
            return
        } else if (previouslyBoundGroup != null) {
            // A different group is being bound; this can happen when an onscreen model is changed.
            // The models or their layouts could have changed, so views may need to be updated

            if (previouslyBoundGroup.models.size > group.models.size) {
                for (i in previouslyBoundGroup.models.size - 1 downTo group.models.size) {
                    removeAndRecycleView(i)
                }
            }
        }

        this.boundGroup = group
        val models = group.models
        val modelCount = models.size

        if (usingStubs() && stubs.size < modelCount) {
            throw IllegalStateException(
                "Insufficient view stubs for EpoxyModelGroup. $modelCount models were provided but only ${stubs.size} view stubs exist."
            )
        }
        viewHolders.ensureCapacity(modelCount)

        for (i in 0 until modelCount) {
            val model = models[i]
            val previouslyBoundModel = previouslyBoundGroup?.models?.getOrNull(i)
            val stubData = stubs.getOrNull(i)
            val parent = stubData?.viewGroup ?: childContainer

            if (previouslyBoundModel != null) {
                if (areSameViewType(previouslyBoundModel, model)) {
                    continue
                }

                removeAndRecycleView(i)
            }

View on GitHub (pinned to e45bd3a61f)