{"record":{"id":"1e07e64318d9a441","repo":"pinpoint-apm/pinpoint","slug":"failed-to-notify-user-group-deletion-listener-lis","errorCode":null,"errorMessage":"Failed to notify user group deletion listener. listener={}, userGroupId={}","messagePattern":"Failed to notify user group deletion listener\\. listener=(.+?), userGroupId=(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"user/src/main/java/com/navercorp/pinpoint/user/service/UserGroupServiceImpl.java","lineNumber":243,"sourceCode":"    }\n\n    private boolean containMemberForUserGroup(String userId, String userGroupId) {\n        List<UserGroupMember> memberList = userGroupDao.selectMember(userGroupId);\n        for (UserGroupMember member : memberList) {\n            if(member.getMemberId().equals(userId)) {\n                return true;\n            }\n        }\n        \n        return false;\n    }\n\n    private void notifyUserGroupDeleted(UserGroup userGroup) {\n        for (UserGroupDeletionListener listener : userGroupDeletionListeners) {\n            try {\n                listener.onUserGroupDeleted(userGroup);\n            } catch (RuntimeException e) {\n                logger.warn(\"Failed to notify user group deletion listener. listener={}, userGroupId={}\",\n                        listener.getClass().getName(), userGroup.getId(), e);\n            }\n        }\n    }\n\n    private void notifyUserGroupDeletedAfterCommit(UserGroup userGroup) {\n        if (userGroupDeletionListeners.isEmpty()) {\n            return;\n        }\n        if (!TransactionSynchronizationManager.isSynchronizationActive()) {\n            notifyUserGroupDeleted(userGroup);\n            return;\n        }\n\n        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {\n            @Override\n            public void afterCommit() {\n                notifyUserGroupDeleted(userGroup);","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/user/src/main/java/com/navercorp/pinpoint/user/service/UserGroupServiceImpl.java#L225-L261","documentation":"UserGroupServiceImpl notifies registered UserGroupDeletionListener beans after a user group is deleted. Each listener is invoked inside a try/catch for RuntimeException; if a listener throws, the failure is logged as a warning ('Failed to notify user group deletion listener') with the listener class and group id, and iteration continues with the remaining listeners. The deletion itself is not rolled back — the error only indicates a side-effect listener failed.","triggerScenarios":"A registered UserGroupDeletionListener's onUserGroupDeleted(userGroup) throws a RuntimeException, e.g. it performs its own DB writes or remote calls that fail while processing deletion of the given user group id.","commonSituations":"A listener that cleans up related data hits a constraint violation or connectivity problem; Spring event/listener bean misconfigured and throwing NPE on the deleted group's id; downstream service (e.g. notification or authorization cache) unavailable at deletion time.","solutions":["Inspect the logged stack trace (the 'e' argument) to see which listener class threw and why","Fix or make resilient the listener identified by listener=<class> in the log (catch its own failures, add retries)","Verify the listener's own transaction/DB state — it runs after the group deletion commit, so referenced data may already be gone","If the side effect matters, add compensating logic or re-run the listener for the affected userGroupId"],"exampleFix":"// before (listener throws when group already deleted)\npublic void onUserGroupDeleted(UserGroup userGroup) {\n    UserGroup group = userGroupService.selectUserGroup(userGroup.getId()); // NPE-prone if already gone\n    cacheService.evict(group.getName());\n}\n// after\npublic void onUserGroupDeleted(UserGroup userGroup) {\n    cacheService.evict(userGroup.getName()); // use the payload, don't re-query\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// listener authors should self-guard; service callers only see the warn log\n@Override\npublic void onUserGroupDeleted(UserGroup userGroup) {\n    try {\n        doCleanup(userGroup.getId());\n    } catch (RuntimeException e) {\n        logger.warn(\"cleanup failed for userGroupId={}\", userGroup.getId(), e);\n        // never propagate: the service already isolates and continues with other listeners\n    }\n}","preventionTips":["Make every UserGroupDeletionListener idempotent and defensive: it runs after the delete commit, so the group row is already gone","Never re-query the deleted group inside the listener; use the payload passed to onUserGroupDeleted","Add retry or outbox handling for listeners with critical side effects (cache eviction, notifications)","Keep listener implementations side-effect-failure tolerant so one bad bean doesn't disrupt user group deletion"],"tags":["pinpoint","listener","side-effect-failure","transaction-callback"],"backgroundTag":"upstream-api-error","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}