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.

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.

Appendix: 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)