JakeWharton/butterknife · error · IllegalStateException

No @%s defined on @%s.

Error message

No @%s defined on @%s.

What it means

The processor recognizes a listener annotation by looking for the @ListenerClass meta-annotation on it, which supplies the target setter method, listener type, and so on. If a listener annotation is processed (typically because it was placed on a method and reached parseListenerAnnotation) but carries no @ListenerClass, this IllegalStateException is thrown. It means the annotation looks like a ButterKnife listener but lacks the metadata the processor needs.

Source

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

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

    // Verify that the method and its containing class are accessible via generated code.
    boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element);
    hasError |= isBindingInWrongPackage(annotationClass, element);

    Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
      error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)",
          annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
    if (listener == null) {
      throw new IllegalStateException(
          String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(),
              annotationClass.getSimpleName()));
    }

    for (int id : ids) {
      if (id == NO_ID.value) {
        if (ids.length == 1) {
          if (!required) {
            error(element, "ID-free binding must not be annotated with @Optional. (%s.%s)",
                enclosingElement.getQualifiedName(), element.getSimpleName());
            hasError = true;
          }
        } else {
          error(element, "@%s annotation contains invalid ID %d. (%s.%s)",
              annotationClass.getSimpleName(), id, enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }

View on GitHub (pinned to fcdebedf32)

Solutions

  1. Add @ListenerClass(...) to the annotation with targetType, setter, type, and either method or callbacks filled in
  2. If the annotation is not meant to be a ButterKnife listener, remove it from the method or from the set of annotations processed by ButterKnifeProcessor

Example fix

// before
public @interface OnCustomEvent {
  int[] value();
}

// after
@ListenerClass(
    targetType = "android.widget.CustomView",
    setter = "setCustomListener",
    type = "com.example.CustomListener",
    method = @ListenerMethod(name = "onCustom", parameters = {View.class}))
public @interface OnCustomEvent {
  int[] value();
}
Defensive patterns

Strategy: validation

Validate before calling

if (MyListener.class.getAnnotation(ListenerClass.class) == null) {
  throw new IllegalStateException("MyListener is missing @ListenerClass");
}

Prevention

When it happens

Trigger: Creating an annotation with a name or package that causes it to be routed to parseListenerAnnotation (e.g. registered via the processor's supported lists) without meta-annotating it with @ListenerClass; deleting the @ListenerClass line while refactoring a custom listener annotation.

Common situations: Writing a custom ButterKnife listener extension for the first time; copy-pasting an annotation class and stripping what looked like an unnecessary meta-annotation.

Related errors


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