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
- Change the target class visibility from private to package-private or public (e.g. `static class MyHolder` instead of `private static class MyHolder`)
- Move the binding fields/methods into a class with at least package visibility
- 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
- Never declare ButterKnife target classes private; use package-private or public
- Avoid binding anonymous/local classes with butterknife-reflect
- Add a lint/unit check that binding targets are not private
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
- More than one bind annotation on
- @BindView fields must extend from View or be an interface. (
- @BindViews List must have a generic component. (
- @BindViews must be a List or array. (
- @BindViews List or array type must extend from View or be an
AI-assisted analysis of JakeWharton/butterknife@fcdebedf32 (2026-08-14).
Data as JSON: /api/errors/1b0572a5ce14e423.
Report an issue: GitHub.