hibernate/hibernate-orm · error · EventListenerRegistrationException
Duplicate event listener found
Error message
Duplicate event listener found
What it means
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.
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
Example fix
// before
registry.getEventListenerGroup(EventType.POST_LOAD)
.appendListeners(myListener); // called twice (two beans / two config paths) -> ERROR action fires
// after
EventListenerGroup<PostLoadEventListener> g = registry.getEventListenerGroup(EventType.POST_LOAD);
boolean present = g.listeners().stream().anyMatch(l -> l instanceof MyListener);
if (!present) g.appendListeners(new MyListener()); Defensive patterns
Strategy: validation
Validate before calling
EventListenerGroup<PersistEventListener> g = registry.getEventListenerGroup(EventType.PERSIST);
boolean already = g.listeners().stream().anyMatch(l -> l instanceof MyListener);
if (!already) {
g.appendListeners(new MyListener());
} Type guard
null
Try / catch
try {
group.appendListeners(listener);
} catch (EventListenerRegistrationException e) {
if ("Duplicate event listener found".equals(e.getMessage())) {
log.debug("listener already registered, skipping");
} else { throw e; }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- BootstrapContext is no longer available
- Could not instantiate event listener
- Duplicate entity definition
- Listener did not implement expected interface [
- Unable to instantiate specified event listener class:
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/8431c8e6750a678d.
Report an issue: GitHub.
Appendix: source
Thrown at hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java:319
// on match - meaning no further strategies are checked...
for ( int i = 0; i < size; i++ ) {
final T existingListener = listenersRead[i];
if ( traceEnabled ) {
EVENT_LISTENER_LOGGER.tracef( "Checking incoming listener [`%s`] for match against existing listener [`%s`]",
listener, existingListener );
}
if ( strategy.areMatch( listener, existingListener ) ) {
if ( traceEnabled ) {
EVENT_LISTENER_LOGGER.tracef( "Found listener match between `%s` and `%s`",
listener, existingListener );
}
final DuplicationStrategy.Action action = strategy.getAction();
switch (action) {
case ERROR:
throw new EventListenerRegistrationException( "Duplicate event listener found" );
case KEEP_ORIGINAL:
if ( traceEnabled ) {
EVENT_LISTENER_LOGGER.tracef( "Skipping listener registration (%s) : `%s`",
action, listener );
}
return;
case REPLACE_ORIGINAL:
if ( traceEnabled ) {
EVENT_LISTENER_LOGGER.tracef( "Replacing listener registration (%s) : `%s` -> `%s`",
action, existingListener, listener );
}
prepareListener( listener );
listenersWrite[i] = listener;
}
// we've found a match - we should return: the match action has already been applied at this point
// apply all pending changes:
setListeners( listenersWrite );View on GitHub (pinned to fad1729dce)