airbnb/epoxy · error · IllegalStateException

Tried to restore instance state, but onSaveInstanceState…

Error message

Tried to restore instance state, but onSaveInstanceState was never called.

What it means

onRestoreInstanceState reads the saved parcelable from the bundle; a non-null bundle without the SAVED_STATE_ARG_VIEW_HOLDERS key means onSaveInstanceState was never called for this adapter, so there is nothing to restore. The library throws to surface the missing save step rather than silently skipping restoration.

Solutions

  1. Ensure you call onSaveInstanceState(Bundle) (or recyclerView.saveState()) on the same adapter before restoring, and store the resulting bundle.
  2. Verify the same adapter/RecyclerView instance saves and restores state — state is not shared across adapters.
  3. Check that the earlier save actually succeeded (it can throw if stable ids are missing).
  4. If restore is optional, guard the call: only restore when the bundle contains the state key.

Example fix

// before
Bundle empty = new Bundle();
adapter.onRestoreInstanceState(empty); // throws: never saved

// after
Bundle saved = new Bundle();
adapter.onSaveInstanceState(saved);
adapter.onRestoreInstanceState(saved);
Defensive patterns

Strategy: validation

Validate before calling

if (bundle != null && bundle.containsKey("saved_view_holders")) { adapter.onRestoreInstanceState(bundle); } // or always pair save+restore on the same instance

Try / catch

try { adapter.onRestoreInstanceState(bundle); } catch (IllegalStateException e) { // state was never saved; continue without restoration }

Prevention

When it happens

Trigger: Calling onRestoreInstanceState with a Bundle that was produced by a different adapter, a fresh empty Bundle, or a bundle where the state key is absent (viewHolderState == null).

Common situations: Saving state on one EpoxyRecyclerView/adapter and restoring on another; passing a manually created Bundle; state was lost because onSaveInstanceState threw earlier (e.g. error [0]) so the key was never written.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/BaseEpoxyAdapter.java:250

      throw new IllegalStateException("Must have stable ids when saving view holder state");
    }

    outState.putParcelable(SAVED_STATE_ARG_VIEW_HOLDERS, viewHolderState);
  }

  public void onRestoreInstanceState(@Nullable Bundle inState) {
    // To simplify things we enforce that state is restored before views are bound, otherwise it
    // is more difficult to update view state once they are bound
    if (boundViewHolders.size() > 0) {
      throw new IllegalStateException(
          "State cannot be restored once views have been bound. It should be done before adding "
              + "the adapter to the recycler view.");
    }

    if (inState != null) {
      viewHolderState = inState.getParcelable(SAVED_STATE_ARG_VIEW_HOLDERS);
      if (viewHolderState == null) {
        throw new IllegalStateException(
            "Tried to restore instance state, but onSaveInstanceState was never called.");
      }
    }
  }

  /**
   * Finds the position of the given model in the list. Doesn't use indexOf to avoid unnecessary
   * equals() calls since we're looking for the same object instance.
   *
   * @return The position of the given model in the current models list, or -1 if the model can't be
   * found.
   */
  protected int getModelPosition(EpoxyModel<?> model) {
    int size = getCurrentModels().size();
    for (int i = 0; i < size; i++) {
      if (model == getCurrentModels().get(i)) {
        return i;
      }

View on GitHub (pinned to e45bd3a61f)