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
- Check ProGuard/R8 keep rules for generated epoxy classes (-keep class **_EpoxyHelper { *; } or similar) so the constructor survives obfuscation.
- Run a clean build so generated helper classes match the current controller code.
- Ensure the controller is a concrete (non-abstract) class.
- 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
- Add ProGuard/R8 keep rules for *_EpoxyHelper classes.
- Match epoxy-compiler and epoxy-adapter versions.
- Clean build after controller renames.
- Avoid abstract controllers at helper lookup time.
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
- Unable to get Epoxy helper class.
- Unable to find Epoxy Helper constructor for
- Failed to hijack update handler in AsyncPagedListDiffer.You…
- Room couldn't find the constructor it is looking for in…
- Must have stable ids when saving view holder state
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)