JakeWharton/butterknife · error · IllegalStateException

Both method() and callback() defined on @%s.

Error message

Both method() and callback() defined on @%s.

What it means

@ListenerClass declares both method() (a default single @ListenerMethod) and callbacks() (an enum of per-constant @ListenerMethod annotations). The processor requires that when method() has exactly one entry, callbacks() must remain the NONE sentinel; any other callbacks class while method() is populated throws this IllegalStateException at processing time. The two mechanisms are mutually exclusive.

Source

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

            hasError = true;
          }
        } else {
          error(element, "@%s annotation contains invalid ID %d. (%s.%s)",
              annotationClass.getSimpleName(), id, enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }
      }
    }

    ListenerMethod method;
    ListenerMethod[] methods = listener.method();
    if (methods.length > 1) {
      throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.",
          annotationClass.getSimpleName()));
    } else if (methods.length == 1) {
      if (listener.callbacks() != ListenerClass.NONE.class) {
        throw new IllegalStateException(
            String.format("Both method() and callback() defined on @%s.",
                annotationClass.getSimpleName()));
      }
      method = methods[0];
    } else {
      Method annotationCallback = annotationClass.getDeclaredMethod("callback");
      Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
      Field callbackField = callback.getDeclaringClass().getField(callback.name());
      method = callbackField.getAnnotation(ListenerMethod.class);
      if (method == null) {
        throw new IllegalStateException(
            String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(),
                annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(),
                callback.name()));
      }
    }

    // Verify that the method has equal to or less than the number of parameters as the listener.

View on GitHub (pinned to fcdebedf32)

Solutions

  1. Remove the method() attribute so the callbacks() enum drives binding
  2. Or reset callbacks to the default by deleting the callbacks attribute if the single method() entry is what you want

Example fix

// before
@ListenerClass(
    method = @ListenerMethod(name = "onClick", parameters = {View.class}),
    callbacks = Callback.class)
public @interface OnThing { ... }

// after
@ListenerClass(callbacks = Callback.class)
public @interface OnThing { ... }
Defensive patterns

Strategy: validation

Validate before calling

ListenerClass lc = OnCustomEvent.class.getAnnotation(ListenerClass.class);
if (lc.method().length == 1 && lc.callbacks() != ListenerClass.NONE.class) {
  throw new IllegalStateException("method() and callbacks() are mutually exclusive");
}

Prevention

When it happens

Trigger: Writing @ListenerClass(method = @ListenerMethod(...), callbacks = MyCallback.class) on a custom listener annotation — filling in both attributes.

Common situations: Starting from a method-based definition and later adding a callbacks enum (or vice versa) without clearing the other attribute; template code that pre-fills both.

Related errors


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