{"record":{"id":"6e7e3cd7d129f969","repo":"hibernate/hibernate-orm","slug":"you-can-only-annotate-one-callback-method-per-call","errorCode":null,"errorMessage":"You can only annotate one callback method per callback type and target class in callback class: %s","messagePattern":"You can only annotate one callback method per callback type and target class in callback class: (.+?)","errorType":"exception","errorClass":"ModelsException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java","lineNumber":843,"sourceCode":"\t}\n\n\tprivate static class TargetedLifecycleEventHandlerBuilder {\n\t\tprivate final EnumMap<CallbackType, MethodDetails> callbackMethods = new EnumMap<>( CallbackType.class );\n\n\t\tprivate TargetedLifecycleEventHandlerBuilder() {\n\t\t}\n\n\t\tprivate void setCallbackMethod(CallbackType callbackType, MethodDetails method) {\n\t\t\tcheckDuplicate( callbackMethods.putIfAbsent( callbackType, method ), method );\n\t\t}\n\n\t\tprivate LifecycleEventHandler build(JpaEventListenerStyle style, ClassDetails listenerClassDetails) {\n\t\t\treturn new LifecycleEventHandler( style, listenerClassDetails, callbackMethods );\n\t\t}\n\n\t\tprivate void checkDuplicate(MethodDetails previous, MethodDetails method) {\n\t\t\tif ( previous != null && previous != method ) {\n\t\t\t\tthrow new ModelsException( \"You can only annotate one callback method per callback type and target class\"\n\t\t\t\t\t\t+ \" in callback class: \" + method.getDeclaringType().getClassName() );\n\t\t\t}\n\t\t}\n\t}\n\n\n\t// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\t// Id generators\n\n\tpublic void collectIdGenerators(JaxbEntityMappingsImpl jaxbRoot) {\n\t\tcollectSequenceGenerators( jaxbRoot.getSequenceGenerators() );\n\t\tcollectTableGenerators( jaxbRoot.getTableGenerators() );\n\t\tcollectGenericGenerators( jaxbRoot.getGenericGenerators() );\n\n\t\t// todo : add support for @IdGeneratorType in mapping.xsd?\n\t}\n\n\tpublic void collectIdGenerators(ClassDetails classDetails) {","sourceCodeStart":825,"sourceCodeEnd":861,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java#L825-L861","documentation":"Within one listener class, each callback type may back only one method per target class. TargetedLifecycleEventHandlerBuilder stores callbacks in a per-callback-type map; when a second, different method with the same callback annotation is registered for the same target entity, checkDuplicate throws naming the listener class.","triggerScenarios":"Two distinct methods in the same listener class both annotated @PrePersist (an overload, or a renamed copy left behind) resolving to the same target entity argument type; likewise two @PostLoad methods, etc.","commonSituations":"Copy-paste plus rename leaving the original annotated method in place; overloads differing in parameter types but targeting the same entity; merging two listener classes into one during refactoring.","solutions":["Delete or un-annotate one of the duplicates - keep exactly one method per callback type in the listener class","If both behaviors are needed, chain them from the single surviving callback or split them into two listener classes","Search the listener class for repeated @Pre*/@Post* annotations of the same kind to catch the second occurrence"],"exampleFix":"// before\npublic class AuditListener {\n    @PrePersist void onCreate(Object e) { log(e); }\n    @PrePersist void onCreateToo(Object e) { audit(e); } // duplicate\n}\n\n// after\npublic class AuditListener {\n    @PrePersist void onCreate(Object e) {\n        log(e);\n        audit(e);\n    }\n}","handlingStrategy":"validation","validationCode":"// Pre-flight: at most one method per callback annotation per listener class\nfor (Class<?> listener : listeners) {\n    Map<Class<? extends Annotation>, Long> counts = new java.util.HashMap<>();\n    for (java.lang.reflect.Method m : listener.getDeclaredMethods())\n        for (var a : m.getAnnotations())\n            counts.merge(a.annotationType(), 1L, Long::sum);\n    counts.forEach((anno, n) -> {\n        if (anno.getName().matches(\".*(Pre|Post)(Persist|Update|Remove|Load)\") && n > 1)\n            throw new IllegalStateException(\"duplicate \" + anno.getSimpleName() + \" in \" + listener);\n    });\n}","typeGuard":null,"tryCatchPattern":"catch (org.hibernate.models.ModelsException e) during bootstrap: the message names the listener class - find the two methods sharing the same callback annotation there and keep only one","preventionTips":["After renaming a callback method, delete the original instead of leaving both annotated","Avoid overloading callback methods in one listener class","When merging listener classes, audit for repeated callback annotations"],"tags":["hibernate","jpa","entity-listener","duplicate-callback","orm"],"backgroundTag":"duplicate-callback-method","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}