airbnb/epoxy · critical · RuntimeException

Unable to invoke

Error message

Unable to invoke {constructor}

What it means

ControllerHelperLookup generates/looks up a generated helper class for each EpoxyController and instantiates it reflectively via Constructor.newInstance(controller). If the reflective call fails with IllegalAccessException or InstantiationException, the library wraps it in a RuntimeException('Unable to invoke <constructor>').

Solutions

  1. Check ProGuard/R8 keep rules for generated epoxy classes (-keep class **_EpoxyHelper { *; } or similar) so the constructor survives obfuscation.
  2. Run a clean build so generated helper classes match the current controller code.
  3. Ensure the controller is a concrete (non-abstract) class.
  4. Verify the epoxy-compiler annotation processor is applied and its version matches epoxy-adapter.

Example fix

// before
// proguard-rules.pro missing epoxy rules -> generated constructor stripped

// after
-keep class **_EpoxyHelper { *; }
-keep class com.airbnb.epoxy.** { *; }
Defensive patterns

Strategy: try-catch

Validate before calling

// build-time: ensure epoxy-compiler is applied and keep rules exist
// -keep class **_EpoxyHelper { <init>(...); }

Try / catch

try { ControllerHelper h = ControllerHelperLookup.getHelperForController(controller); } catch (RuntimeException e) { throw new IllegalStateException("Epoxy helper init failed for " + controller.getClass(), e.getCause()); }

Prevention

When it happens

Trigger: The generated helper class exists but its constructor cannot be invoked: constructor not accessible (non-public generated class/constructor with setAccessible failing under module/visibility restrictions), abstract or interface controller class, or constructor signature mismatch.

Common situations: Kotlin/Java visibility or obfuscation (ProGuard/R8 renaming or stripping the generated constructor); running on a classpath where the annotation processor output is stale or missing so the found class doesn't match the controller; instantiating an abstract controller.

Related errors


AI-assisted analysis of airbnb/epoxy@e45bd3a61f (2026-09-13). Data as JSON: /api/errors/1e0a313d9b4f9d52. Report an issue: GitHub.

Appendix: source

Thrown at epoxy-adapter/src/main/java/com/airbnb/epoxy/ControllerHelperLookup.java:29

 * Looks up a generated {@link ControllerHelper} implementation for a given adapter.
 * If the adapter has no {@link com.airbnb.epoxy.AutoModel} models then a No-Op implementation will
 * be returned.
 */
class ControllerHelperLookup {
  private static final String GENERATED_HELPER_CLASS_SUFFIX = "_EpoxyHelper";
  private static final Map<Class<?>, Constructor<?>> BINDINGS = new LinkedHashMap<>();
  private static final NoOpControllerHelper NO_OP_CONTROLLER_HELPER = new NoOpControllerHelper();

  static ControllerHelper getHelperForController(EpoxyController controller) {
    Constructor<?> constructor = findConstructorForClass(controller.getClass());
    if (constructor == null) {
      return NO_OP_CONTROLLER_HELPER;
    }

    try {
      return (ControllerHelper) constructor.newInstance(controller);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to get Epoxy helper class.", cause);
    }
  }

  @Nullable
  private static Constructor<?> findConstructorForClass(Class<?> controllerClass) {
    Constructor<?> helperCtor = BINDINGS.get(controllerClass);
    if (helperCtor != null || BINDINGS.containsKey(controllerClass)) {

View on GitHub (pinned to e45bd3a61f)