{"record":{"id":"8431c8e6750a678d","repo":"hibernate/hibernate-orm","slug":"duplicate-event-listener-found","errorCode":null,"errorMessage":"Duplicate event listener found","messagePattern":"Duplicate event listener found","errorType":"exception","errorClass":"EventListenerRegistrationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java","lineNumber":319,"sourceCode":"\t\t\t//\t\ton match - meaning no further strategies are checked...\n\n\t\t\tfor ( int i = 0; i < size; i++ ) {\n\t\t\t\tfinal T existingListener = listenersRead[i];\n\t\t\t\tif ( traceEnabled ) {\n\t\t\t\t\tEVENT_LISTENER_LOGGER.tracef( \"Checking incoming listener [`%s`] for match against existing listener [`%s`]\",\n\t\t\t\t\t\t\tlistener, existingListener );\n\t\t\t\t}\n\n\t\t\t\tif ( strategy.areMatch( listener,  existingListener ) ) {\n\t\t\t\t\tif ( traceEnabled ) {\n\t\t\t\t\t\tEVENT_LISTENER_LOGGER.tracef( \"Found listener match between `%s` and `%s`\",\n\t\t\t\t\t\t\t\tlistener, existingListener );\n\t\t\t\t\t}\n\n\t\t\t\t\tfinal DuplicationStrategy.Action action = strategy.getAction();\n\t\t\t\t\tswitch (action) {\n\t\t\t\t\t\tcase ERROR:\n\t\t\t\t\t\t\tthrow new EventListenerRegistrationException( \"Duplicate event listener found\" );\n\t\t\t\t\t\tcase KEEP_ORIGINAL:\n\t\t\t\t\t\t\tif ( traceEnabled ) {\n\t\t\t\t\t\t\t\tEVENT_LISTENER_LOGGER.tracef( \"Skipping listener registration (%s) : `%s`\",\n\t\t\t\t\t\t\t\t\t\taction, listener );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tcase REPLACE_ORIGINAL:\n\t\t\t\t\t\t\tif ( traceEnabled ) {\n\t\t\t\t\t\t\t\tEVENT_LISTENER_LOGGER.tracef( \"Replacing listener registration (%s) : `%s` -> `%s`\",\n\t\t\t\t\t\t\t\t\t\taction, existingListener, listener );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tprepareListener( listener );\n\t\t\t\t\t\t\tlistenersWrite[i] = listener;\n\t\t\t\t\t}\n\n\t\t\t\t\t// we've found a match - we should return: the match action has already been applied at this point\n\t\t\t\t\t// apply all pending changes:\n\t\t\t\t\tsetListeners( listenersWrite );","sourceCodeStart":301,"sourceCodeEnd":337,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java#L301-L337","documentation":"EventListenerRegistrationException thrown while appending a listener to an EventListenerGroup: a registered DuplicationStrategy matched the new listener against an already-registered one (strategy.areMatch(listener, existingListener)), and the strategy's configured action is ERROR — meaning duplicates for this event type are forbidden and registration aborts. This is the mechanism that stops the same logical listener from being wired into an event chain twice.","triggerScenarios":"Calling EventListenerRegistry.getEventListenerGroup(type).appendListeners(...) twice with the same listener instance/class; two integrations (e.g. Envers plus a custom audit integrator) both registering listeners for the same EventType where a strategy like DefaultDuplicationStrategy(CLASS_EQUAL) returns ERROR; adding a listener in both hibernate.cfg.xml and an Integrator so both run at bootstrap.","commonSituations":"A custom Integrator discovered twice (duplicate jar on classpath, or registered both via META-INF/services and programmatically); Spring configuration building the same listener bean into two registry paths; copy-pasted bootstrap code registering the same listener in two places; version upgrades where a bundled integration now registers a listener your code also registers.","solutions":["Audit registration paths and register each listener exactly once (check for duplicated Integrator jars / Spring bean wiring; mvn dependency:tree for doubled integrations)","Use setListeners(type, listeners...) to replace the whole chain for that event type instead of appending, so re-runs cannot stack duplicates","If the overlap is intentional, register a DuplicationStrategy with Action.KEEP_ORIGINAL or REPLACE_ORIGINAL on the group before appending"],"exampleFix":"// before\nregistry.getEventListenerGroup(EventType.POST_LOAD)\n        .appendListeners(myListener); // called twice (two beans / two config paths) -> ERROR action fires\n\n// after\nEventListenerGroup<PostLoadEventListener> g = registry.getEventListenerGroup(EventType.POST_LOAD);\nboolean present = g.listeners().stream().anyMatch(l -> l instanceof MyListener);\nif (!present) g.appendListeners(new MyListener());","handlingStrategy":"validation","validationCode":"EventListenerGroup<PersistEventListener> g = registry.getEventListenerGroup(EventType.PERSIST);\nboolean already = g.listeners().stream().anyMatch(l -> l instanceof MyListener);\nif (!already) {\n    g.appendListeners(new MyListener());\n}","typeGuard":"null","tryCatchPattern":"try {\n    group.appendListeners(listener);\n} catch (EventListenerRegistrationException e) {\n    if (\"Duplicate event listener found\".equals(e.getMessage())) {\n        log.debug(\"listener already registered, skipping\");\n    } else { throw e; }\n}","preventionTips":["Register listeners from exactly one place (one Integrator, one Spring config class)","Check group.listeners() for an existing instance before appending","Use setListeners when you want deterministic, idempotent replacement"],"tags":["hibernate","event-listener","registration","duplicate","integrator","bootstrap"],"backgroundTag":"duplicate-listener-registration","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}