gradle/gradle · error · ListenerNotificationException

Failed to notify {typeDescription}.

Error message

Failed to notify {typeDescription}.

What it means

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.

Source

Thrown at platforms/core-runtime/messaging/src/main/java/org/gradle/internal/event/AbstractBroadcastDispatch.java:46

import java.util.Locale;

public abstract class AbstractBroadcastDispatch<T> implements Dispatch<MethodInvocation> {
    protected final Class<T> type;

    public AbstractBroadcastDispatch(Class<T> type) {
        this.type = type;
    }

    private String getErrorMessage() {
        String typeDescription = type.getSimpleName().replaceAll("(\\p{Upper})", " $1").trim().toLowerCase(Locale.ROOT);
        return "Failed to notify " + typeDescription + ".";
    }

    protected void dispatch(MethodInvocation invocation, Dispatch<MethodInvocation> handler) {
        try {
            handler.dispatch(invocation);
        } catch (UncheckedException e) {
            throw new ListenerNotificationException(invocation, getErrorMessage(), Collections.singletonList(e.getCause()));
        } catch (BuildOperationInvocationException e) {
            throw new ListenerNotificationException(invocation, getErrorMessage(), Collections.singletonList(e.getCause()));
        } catch (RuntimeException t) {
            throw t;
        } catch (Throwable t) {
            throw new ListenerNotificationException(invocation, getErrorMessage(), Collections.singletonList(t));
        }
    }

    /**
     * Dispatch an invocation to the given dispatchers.
     * <p>
     * This method will try to dispatch the invocation in an efficient way based on the number of dispatchers.
     * </p>
     */
    protected void dispatch(MethodInvocation invocation, List<? extends Dispatch<MethodInvocation>> dispatchers) {
        switch (dispatchers.size()) {
            case 0:

View on GitHub (pinned to 534f27719b)

Solutions

  1. Read the wrapped cause (first element of the exception's causes) — its stack trace names the failing listener class and method
  2. Fix the listener: guard nulls and event types so callbacks never throw
  3. Remove or update the offending plugin if you do not own the listener code
  4. If you own the dispatch site, catch ListenerNotificationException and process all its causes instead of losing them

Example fix

// before
public void afterTest(TestDescriptor td, TestResult tr) {
    report.record(td.getDisplayName().length()); // NPE when displayName is null
}

// after
public void afterTest(TestDescriptor td, TestResult tr) {
    String name = td.getDisplayName();
    if (name != null) report.record(name.length());
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    broadcast.dispatch(invocation);
} catch (ListenerNotificationException e) {
    Throwable cause = e.getCauses().get(0); // original listener failure, unwrapped from UncheckedException
    LOGGER.error("Notification failed", cause);
    // decide: rethrow, or continue if the build must survive listener errors
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gradle/gradle@534f27719b (2026-08-22). Data as JSON: /api/errors/5fa92ae980a788ca. Report an issue: GitHub.