airbnb/epoxy · error · IllegalStateException
Group is not bound
Error message
Group is not bound
What it means
unbindGroup releases the child ViewHolders created for the currently bound EpoxyModelGroup and clears boundGroup. If unbindGroup is called when no group is bound (boundGroup == null), there is nothing to release, and the holder throws this IllegalStateException to surface a lifecycle misuse.
Solutions
- Track bind state in your code and only call unbindGroup after a successful bind.
- Make unbind idempotent on your side by checking your own bound flag before delegating.
- If you control the calling code, remove the redundant unbind call (RecyclerView already calls unbind at the right time).
- Catch IllegalStateException around unbindGroup only if double-unbind is expected in your pool design.
Example fix
// before
holder.unbind()
holder.unbind() // second call throws
// after
if (holder.boundGroup != null) {
holder.unbind()
} Defensive patterns
Strategy: type-guard
Type guard
fun ModelGroupHolder.safeUnbind() {
if (this.boundGroup != null) this.unbind()
} Prevention
- Let RecyclerView drive bind/unbind; avoid manual unbind calls.
- Keep a boolean bound flag in wrapper code and make your unbind idempotent.
- In custom pools, unbind only in onViewRecycled, not also in onDetachedFromWindow.
When it happens
Trigger: Calling unbindGroup() (or unbind()) twice in a row without an intervening bind; calling unbind on a holder that was never bound.
Common situations: Double-unbind from overlapping RecyclerView lifecycle callbacks (onViewRecycled plus onViewDetachedFromWindow); manual holder management where bind was skipped; custom pool/recycling code calling unbind defensively.
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
- Can only call this when inside the `buildModels` method
- Cannot call this from inside `buildModels`
- This holder is not currently bound.
- You cannot call `buildModels` directly. Call `setModels`…
- You cannot call `requestModelBuild` directly. Call…
AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13).
Data as JSON: /api/errors/68e28c87691248ae.
Report an issue: GitHub.
Appendix: source
Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelGroupHolder.kt:162
return ViewTypeManager.getViewType(model1) == ViewTypeManager.getViewType(model2)
}
private fun getViewHolder(parent: ViewGroup, model: EpoxyModel<*>): EpoxyViewHolder {
val viewType = ViewTypeManager.getViewType(model)
val recycledView = viewPool.getRecycledView(viewType)
return recycledView as? EpoxyViewHolder
?: HELPER_ADAPTER.createViewHolder(
modelGroupParent,
model,
parent,
viewType
)
}
fun unbindGroup() {
if (boundGroup == null) {
throw IllegalStateException("Group is not bound")
}
repeat(viewHolders.size) {
// Remove from the end for more efficient list actions
removeAndRecycleView(viewHolders.size - 1)
}
boundGroup = null
}
private fun removeAndRecycleView(modelPosition: Int) {
if (usingStubs()) {
stubs[modelPosition].resetStub()
} else {
childContainer.removeViewAt(modelPosition)
}
val viewHolder = viewHolders.removeAt(modelPosition)View on GitHub (pinned to e45bd3a61f)