{"record":{"id":"79ca58bd21f5b9f1","repo":"SonarSource/sonarqube","slug":"onchange-call-failed-on-listener-s-for-events","errorCode":null,"errorMessage":"onChange() call failed on listener %s for events %s","messagePattern":"onChange\\(\\) call failed on listener (.+?) for events (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java","lineNumber":111,"sourceCode":"    branchQgChangeEvents.forEach(changeEvent -> broadcastChangeEventToListeners(changedIssues, changeEvent));\n  }\n\n  private static ImmutableSet<ChangedIssue> toChangedIssues(Collection<DefaultIssue> defaultIssues, boolean fromAlm) {\n    return defaultIssues.stream()\n      .map(defaultIssue -> new ChangedIssueImpl(defaultIssue, fromAlm))\n      .collect(toImmutableSet());\n  }\n\n  private void broadcastChangeEventToListeners(Set<ChangedIssue> changedIssues, QGChangeEvent changeEvent) {\n    listeners.forEach(listener -> broadcastChangeEventToListener(changedIssues, changeEvent, listener));\n  }\n\n  private static void broadcastChangeEventToListener(Set<ChangedIssue> changedIssues, QGChangeEvent changeEvent, QGChangeEventListener listener) {\n    try {\n      LOG.trace(\"calling onChange() on listener {} for events {}...\", listener.getClass().getName(), changeEvent);\n      listener.onIssueChanges(changeEvent, changedIssues);\n    } catch (Exception e) {\n      LOG.warn(format(\"onChange() call failed on listener %s for events %s\", listener.getClass().getName(), changeEvent), e);\n    }\n  }\n\n}\n","sourceCodeStart":93,"sourceCodeEnd":116,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java#L93-L116","documentation":"broadcastChangeEventToListener wraps each individual listener.onIssueChanges() call in try/catch(Exception). If a listener throws a regular Exception, it is logged as a warning naming the listener class and the event, so one broken listener does not affect others.","triggerScenarios":"Any registered QGChangeEventListener whose onIssueChanges() implementation throws (DB failure, NPE, REST call failure inside the listener) during broadcastOnAnyChange or branch broadcast.","commonSituations":"Third-party webhooks/notification listener plugins failing on malformed events; a listener hitting a database outage while processing quality-gate changes.","solutions":["Identify the listener class named in the log message and inspect its stack trace.","Fix the bug in the custom listener (or report it to the plugin maintainer).","Disable/uninstall the failing listener plugin if it cannot be fixed.","Verify downstream effects (e.g. notifications/webhooks) and re-run the affected computation if events were dropped."],"exampleFix":"// inside a custom listener\n// before\npublic void onIssueChanges(QGChangeEvent event, Set<ChangedIssue> issues) { db.sessions()... }\n// after\npublic void onIssueChanges(QGChangeEvent event, Set<ChangedIssue> issues) {\n  try (DbSession s = db.openSession(false)) { ... }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// in your custom listener implementation\n@Override\npublic void onIssueChanges(QGChangeEvent e, Set<ChangedIssue> issues) {\n  try { doWork(e, issues); }\n  catch (Exception ex) { LOG.warn(\"onChange failed\", ex); }\n}","preventionTips":["Never let exceptions escape listener.onIssueChanges implementations","Guard DB sessions and external calls inside listeners","Log and isolate per-event failures so one event does not abort the batch"],"tags":["sonarqube","quality-gate","listeners","exception-handling"],"backgroundTag":"upstream-api-error","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}