{"record":{"id":"5fa92ae980a788ca","repo":"gradle/gradle","slug":"failed-to-notify-typedescription","errorCode":null,"errorMessage":"Failed to notify {typeDescription}.","messagePattern":"Failed to notify (.+?)\\.","errorType":"exception","errorClass":"ListenerNotificationException","httpStatus":null,"severity":"error","filePath":"platforms/core-runtime/messaging/src/main/java/org/gradle/internal/event/AbstractBroadcastDispatch.java","lineNumber":46,"sourceCode":"import java.util.Locale;\n\npublic abstract class AbstractBroadcastDispatch<T> implements Dispatch<MethodInvocation> {\n    protected final Class<T> type;\n\n    public AbstractBroadcastDispatch(Class<T> type) {\n        this.type = type;\n    }\n\n    private String getErrorMessage() {\n        String typeDescription = type.getSimpleName().replaceAll(\"(\\\\p{Upper})\", \" $1\").trim().toLowerCase(Locale.ROOT);\n        return \"Failed to notify \" + typeDescription + \".\";\n    }\n\n    protected void dispatch(MethodInvocation invocation, Dispatch<MethodInvocation> handler) {\n        try {\n            handler.dispatch(invocation);\n        } catch (UncheckedException e) {\n            throw new ListenerNotificationException(invocation, getErrorMessage(), Collections.singletonList(e.getCause()));\n        } catch (BuildOperationInvocationException e) {\n            throw new ListenerNotificationException(invocation, getErrorMessage(), Collections.singletonList(e.getCause()));\n        } catch (RuntimeException t) {\n            throw t;\n        } catch (Throwable t) {\n            throw new ListenerNotificationException(invocation, getErrorMessage(), Collections.singletonList(t));\n        }\n    }\n\n    /**\n     * Dispatch an invocation to the given dispatchers.\n     * <p>\n     * This method will try to dispatch the invocation in an efficient way based on the number of dispatchers.\n     * </p>\n     */\n    protected void dispatch(MethodInvocation invocation, List<? extends Dispatch<MethodInvocation>> dispatchers) {\n        switch (dispatchers.size()) {\n            case 0:","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/gradle/gradle/blob/534f27719b66953f95cc907aae7f2c1b12f5482d/platforms/core-runtime/messaging/src/main/java/org/gradle/internal/event/AbstractBroadcastDispatch.java#L28-L64","documentation":"AbstractBroadcastDispatch wraps every listener notification; when a listener method wrapped in UncheckedException throws, dispatch(...) unwraps the cause and rethrows it inside a ListenerNotificationException whose message is 'Failed to notify <type description>.' — the listener interface's simple name split on camel case and lowercased (TestListener -> 'test listener'). This keeps one listener's failure from silently vanishing while branding it as a notification problem.","triggerScenarios":"Any registered listener throwing an UncheckedException-wrapped error during a broadcast notification, e.g. a TestListener failing inside afterTest or a BuildListener failing in buildFinished.","commonSituations":"Plugin listeners throwing NPEs on unexpected input; listeners assuming non-null domain objects that later Gradle versions make nullable; test listeners breaking on new event types.","solutions":["Read the wrapped cause (first element of the exception's causes) — its stack trace names the failing listener class and method","Fix the listener: guard nulls and event types so callbacks never throw","Remove or update the offending plugin if you do not own the listener code","If you own the dispatch site, catch ListenerNotificationException and process all its causes instead of losing them"],"exampleFix":"// before\npublic void afterTest(TestDescriptor td, TestResult tr) {\n    report.record(td.getDisplayName().length()); // NPE when displayName is null\n}\n\n// after\npublic void afterTest(TestDescriptor td, TestResult tr) {\n    String name = td.getDisplayName();\n    if (name != null) report.record(name.length());\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    broadcast.dispatch(invocation);\n} catch (ListenerNotificationException e) {\n    Throwable cause = e.getCauses().get(0); // original listener failure, unwrapped from UncheckedException\n    LOGGER.error(\"Notification failed\", cause);\n    // decide: rethrow, or continue if the build must survive listener errors\n}","preventionTips":["Keep listener callback bodies exception-free: guard nulls and instanceof before casting","Log and return from listeners instead of throwing","Test listeners against the exact Gradle version you target, since event contracts evolve"],"tags":["gradle","listeners","events","broadcast","notification"],"backgroundTag":"listener-notification-failure","analyzedSha":"534f27719b66953f95cc907aae7f2c1b12f5482d","analyzedAt":"2026-08-22T08:09:12.375Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}