JakeWharton/butterknife · error · IllegalStateException

@%s annotation must be on a method.

Error message

@%s annotation must be on a method.

What it means

ButterKnife's processor only accepts listener annotations (e.g. @OnClick, @OnCheckedChanged, or any annotation meta-annotated with @ListenerClass) on methods. Before casting the element to ExecutableElement it verifies the element is an executable of kind METHOD; otherwise this IllegalStateException is thrown. Although @Target(METHOD) normally prevents this, the processor keeps the check for safe casting.

Source

Thrown at butterknife-compiler/src/main/java/butterknife/compiler/ButterKnifeProcessor.java:1047

      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        StringWriter stackTrace = new StringWriter();
        e.printStackTrace(new PrintWriter(stackTrace));

        error(element, "Unable to generate view binder for @%s.\n\n%s",
            annotationClass.getSimpleName(), stackTrace.toString());
      }
    }
  }

  private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element,
      Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames)
      throws Exception {
    // This should be guarded by the annotation's @Target but it's worth a check for safe casting.
    if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) {
      throw new IllegalStateException(
          String.format("@%s annotation must be on a method.", annotationClass.getSimpleName()));
    }

    ExecutableElement executableElement = (ExecutableElement) element;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Assemble information on the method.
    Annotation annotation = element.getAnnotation(annotationClass);
    Method annotationValue = annotationClass.getDeclaredMethod("value");
    if (annotationValue.getReturnType() != int[].class) {
      throw new IllegalStateException(
          String.format("@%s annotation value() type not int[].", annotationClass));
    }

    int[] ids = (int[]) annotationValue.invoke(annotation);
    String name = executableElement.getSimpleName().toString();
    boolean required = isListenerRequired(executableElement);

View on GitHub (pinned to fcdebedf32)

Solutions

  1. Move the annotation onto a method (e.g. a public void onButtonClick(View v) method in the activity/fragment)
  2. For field-style injection use @BindView instead of a listener annotation
  3. If you own the annotation, declare @Target(ElementType.METHOD) on it so the compiler rejects misuse earlier

Example fix

// before
@OnClick(R.id.submit)
Button submitButton; // annotation on a field

// after
@BindView(R.id.submit)
Button submitButton;

@OnClick(R.id.submit)
void onSubmitClicked(View v) { ... }
Defensive patterns

Strategy: validation

Type guard

// Annotation declaration itself guards usage — restrict targets
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnCustomEvent { int[] value(); }

Prevention

When it happens

Trigger: Applying a listener annotation to a field, constructor, or class in a project where the annotation's @Target allows more than METHOD; using a custom listener annotation declared with @Target({FIELD, METHOD}); applying @OnClick to a Kotlin property backing field exposed as a field element.

Common situations: Kotlin code where @OnClick is placed on a property or the annotation is attached in Java to a field by accident; custom listener annotations copied from samples with a loose @Target.

Related errors


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