{"record":{"id":"3b98ceb90d40b33e","repo":"hibernate/hibernate-orm","slug":"callback-method-annotated-s-in-s-must-retur","errorCode":null,"errorMessage":"Callback method annotated '@%s' in '%s' must return void and accept one argument: %s","messagePattern":"Callback method annotated '@(.+?)' in '(.+?)' must return void and accept one argument: (.+?)","errorType":"exception","errorClass":"ModelsException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java","lineNumber":815,"sourceCode":"\t}\n\n\tprivate void addTargetedJpaEventListener(ClassDetails targetClass, LifecycleEventHandler listener) {\n\t\tif ( targetedLifecycleEventHandlers == null ) {\n\t\t\ttargetedLifecycleEventHandlers = new LinkedHashMap<>();\n\t\t}\n\n\t\ttargetedLifecycleEventHandlers.computeIfAbsent( targetClass, ignored -> new ArrayList<>() ).add( listener );\n\t}\n\n\tprivate static void applyTargetedCallback(\n\t\t\tClassDetails listenerClassDetails,\n\t\t\tMethodDetails methodDetails,\n\t\t\tClass<? extends Annotation> callbackAnnotation,\n\t\t\tCallbackType callbackType,\n\t\t\tMap<ClassDetails, TargetedLifecycleEventHandlerBuilder> builders) {\n\t\tif ( methodDetails.hasDirectAnnotationUsage( callbackAnnotation ) ) {\n\t\t\tif ( !LifecycleEventHandler.matchesSignature( JpaEventListenerStyle.LISTENER, methodDetails ) ) {\n\t\t\t\tthrow new ModelsException( \"Callback method annotated '@\"\n\t\t\t\t\t\t+ callbackAnnotation.getSimpleName() + \"' in '\"\n\t\t\t\t\t\t+ listenerClassDetails.getClassName()\n\t\t\t\t\t\t+ \"' must return void and accept one argument: \" + methodDetails );\n\t\t\t}\n\n\t\t\tbuilders.computeIfAbsent( methodDetails.getArgumentTypes().get( 0 ),\n\t\t\t\t\t\t\tignored -> new TargetedLifecycleEventHandlerBuilder() )\n\t\t\t\t\t.setCallbackMethod( callbackType, methodDetails );\n\t\t}\n\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) {","sourceCodeStart":797,"sourceCodeEnd":833,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java#L797-L833","documentation":"Every entity-listener callback method must follow the JPA signature: return void and accept exactly one argument (the entity). GlobalRegistrationsImpl.applyTargetedCallback checks each method carrying a callback annotation via LifecycleEventHandler.matchesSignature and throws naming the annotation, the listener class, and the offending method details.","triggerScenarios":"A listener method annotated with a callback annotation that returns a value (boolean/String), takes zero arguments, or takes two or more - e.g. boolean beforeUpdate(Item item, EntityManager em) annotated @PreUpdate.","commonSituations":"Porting boolean 'validate' style hooks from other frameworks; adding context parameters (EntityManager, audit context) to callbacks; converting interceptors to JPA listeners without adjusting signatures.","solutions":["Change the method to return void and accept exactly one parameter typed as the entity (or Object): @PreUpdate void onUpdate(Item item)","Obtain any extra context inside the method body (injected beans, static helpers) rather than as parameters","Leave non-conforming helper methods unannotated - only real callbacks should carry the annotation"],"exampleFix":"// before\n@PreUpdate\nboolean beforeUpdate(Item item, EntityManager em) { return item.isValid(); }\n\n// after\n@PreUpdate\nvoid beforeUpdate(Item item) {\n    if ( !item.isValid() ) throw new IllegalStateException(\"invalid item\");\n}","handlingStrategy":"validation","validationCode":"// Pre-flight: validate every @Pre*/@Post* method in listener classes\nfor (Class<?> listener : listeners) {\n    for (java.lang.reflect.Method m : listener.getDeclaredMethods()) {\n        boolean isCallback = java.util.Arrays.stream(m.getAnnotations())\n                .anyMatch(a -> a.annotationType().getName().startsWith(\"jakarta.persistence.\"));\n        if (isCallback && !isValidCallback(m))\n            throw new IllegalStateException(\"Bad callback signature: \" + m);\n    }\n}","typeGuard":"// Narrows a reflection Method to a valid JPA entity-listener callback\nstatic boolean isValidCallback(java.lang.reflect.Method m) {\n    return m.getReturnType() == void.class && m.getParameterCount() == 1;\n}","tryCatchPattern":"catch (org.hibernate.models.ModelsException e) during bootstrap: the message names the annotation, class and method - change that method to return void with exactly one entity parameter","preventionTips":["Memorize the JPA callback contract: void return, exactly one entity argument","Fetch extra context inside the method body, never via extra parameters","Add a reflection-based test that validates all listener methods at build time"],"tags":["hibernate","jpa","entity-listener","callback-signature","orm"],"backgroundTag":"jpa-callback-signature","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}