JakeWharton/butterknife · error · IllegalArgumentException

must not be private.

Error message

 must not be private.

What it means

ButterKnife.bind(target, source) in the reflection-based butterknife-reflect module walks the class hierarchy and calls setAccessible(true) on annotated members. A private top-level (or local/anonymous) target class cannot be safely reflectively accessed from the binder machinery, so bind fails fast with an IllegalArgumentException before any binding happens.

Source

Thrown at butterknife-reflect/src/main/java/butterknife/ButterKnife.java:135

  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
    View sourceView = source.getWindow().getDecorView();
    return bind(target, sourceView);
  }

  /**
   * BindView annotated fields and methods in the specified {@code target} using the {@code source}
   * {@link View} as the view root.
   *
   * @param target Target class for view binding.
   * @param source View root on which IDs will be looked up.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    List<Unbinder> unbinders = new ArrayList<>();
    Class<?> targetClass = target.getClass();
    if ((targetClass.getModifiers() & PRIVATE) != 0) {
      throw new IllegalArgumentException(targetClass.getName() + " must not be private.");
    }

    while (true) {
      String clsName = targetClass.getName();
      if (clsName.startsWith("android.") || clsName.startsWith("java.")
          || clsName.startsWith("androidx.")) {
        break;
      }

      for (Field field : targetClass.getDeclaredFields()) {
        int unbinderStartingSize = unbinders.size();
        Unbinder unbinder;

        unbinder = parseBindView(target, field, source);
        if (unbinder != null) unbinders.add(unbinder);

        unbinder = parseBindViews(target, field, source);
        if (unbinder != null) unbinders.add(unbinder);

View on GitHub (pinned to fcdebedf32)

Solutions

  1. Change the target class visibility from private to package-private or public (e.g. `static class MyHolder` instead of `private static class MyHolder`)
  2. Move the binding fields/methods into a class with at least package visibility
  3. Prefer the annotation-processor (generated binder) flavor of ButterKnife, which does not reflect on the target class itself

Example fix

// before
private static class Header {
  @BindView(R.id.title) TextView title;
}

// after
static class Header {
  @BindView(R.id.title) TextView title;
}
Defensive patterns

Strategy: validation

Validate before calling

Class<?> cls = target.getClass();
if ((cls.getModifiers() & Modifier.PRIVATE) != 0) {
  throw new IllegalArgumentException("Refuse to bind private class " + cls.getName());
}
ButterKnife.bind(target, source);

Try / catch

try {
  ButterKnife.bind(target, source);
} catch (IllegalArgumentException e) {
  if (e.getMessage() != null && e.getMessage().endsWith("must not be private.")) {
    logAndRecoverFromPrivateTarget(e); // e.g. skip binding for this target
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling ButterKnife.bind(new PrivateTarget(), view) where PrivateTarget is declared `private static class`; binding on an anonymous class instance created inside another class; Java 15+ hidden classes.

Common situations: Using butterknife-reflect (or the ReflectUnbinder path in tests) with nested private classes; refactoring a public binding class to private while keeping bind calls; IDE-generating a private inner class for a listener holder.

Related errors


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