JakeWharton/butterknife · error · IllegalStateException

Parameter #{} of method '{}' was of the wrong type for param

Error message

Parameter #{} of method '{}' was of the wrong type for parameter #{} of method '{}'. See cause for more info.

What it means

Thrown by Utils.castParam during listener-method argument wiring when a parameter of an @On* method cannot be cast to the parameter type the framework callback actually supplies. The original ClassCastException is the cause; the message names both methods (from/to) and both parameter positions so the mismatch is pinpointed.

Source

Thrown at butterknife-runtime/src/main/java/butterknife/internal/Utils.java:120

      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
  }

  public static <T> T castParam(Object value, String from, int fromPos, String to, int toPos,
      Class<T> cls) {
    try {
      return cls.cast(value);
    } catch (ClassCastException e) {
      throw new IllegalStateException("Parameter #"
          + (fromPos + 1)
          + " of method '"
          + from
          + "' was of the wrong type for parameter #"
          + (toPos + 1)
          + " of method '"
          + to
          + "'. See cause for more info.", e);
    }
  }

  private static String getResourceEntryName(View view, @IdRes int id) {
    if (view.isInEditMode()) {
      return "<unavailable while editing>";
    }
    return view.getContext().getResources().getResourceEntryName(id);
  }

View on GitHub (pinned to fcdebedf32)

Solutions

  1. Match the framework callback's parameter types exactly or accept a supertype (CharSequence instead of String, Object instead of a model class).
  2. Cast inside the method body (`((String) s)`) only when you control the producer and know the runtime type.
  3. Copy signatures from the wrapped listener interface docs (TextWatcher, AdapterView.OnItemClickListener, etc.).

Example fix

// before
@OnTextChanged(R.id.search) void onSearch(String text) { }

// after
@OnTextChanged(R.id.search) void onSearch(CharSequence text) { }
Defensive patterns

Strategy: type-guard

Validate before calling

Class<?>[] cb = {CharSequence.class, int.class, int.class, int.class};
for (Class<?> p : method.getParameterTypes()) {
  boolean ok = false;
  for (Class<?> c : cb) ok |= c.isAssignableFrom(p);
  if (!ok) throw new IllegalStateException(p + " cannot receive any callback param");
}

Type guard

static boolean canReceive(Class<?> callbackParam, Class<?> methodParam) {
  return callbackParam.isAssignableFrom(methodParam) || methodParam == callbackParam;
}

Prevention

When it happens

Trigger: With the reflect binder's argument matching or generated code, a listener parameter whose declared type is narrower than the callback's: e.g. `@OnTextChanged(R.id.q) void onText(String s)` (callback passes CharSequence), or `@OnItemClick void pick(String item)` where the adapter supplies Object — ClassCastException at castParam during bind/registration.

Common situations: Writing listener signatures from memory instead of the framework interface (String vs CharSequence is the classic case); custom adapters whose getItem returns Object; AdapterView callbacks delivering a generic type after a library update changed the callback signature.

Related errors


AI-assisted analysis of JakeWharton/butterknife@fcdebedf32 (2026-08-14). Data as JSON: /api/errors/6d3ac81b399f5a0f. Report an issue: GitHub.