hibernate/hibernate-orm · error · ModelsException
Callback method annotated '@%s' in '%s' must return void and
Error message
Callback method annotated '@%s' in '%s' must return void and accept one argument: %s
What it means
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.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java:815
}
private void addTargetedJpaEventListener(ClassDetails targetClass, LifecycleEventHandler listener) {
if ( targetedLifecycleEventHandlers == null ) {
targetedLifecycleEventHandlers = new LinkedHashMap<>();
}
targetedLifecycleEventHandlers.computeIfAbsent( targetClass, ignored -> new ArrayList<>() ).add( listener );
}
private static void applyTargetedCallback(
ClassDetails listenerClassDetails,
MethodDetails methodDetails,
Class<? extends Annotation> callbackAnnotation,
CallbackType callbackType,
Map<ClassDetails, TargetedLifecycleEventHandlerBuilder> builders) {
if ( methodDetails.hasDirectAnnotationUsage( callbackAnnotation ) ) {
if ( !LifecycleEventHandler.matchesSignature( JpaEventListenerStyle.LISTENER, methodDetails ) ) {
throw new ModelsException( "Callback method annotated '@"
+ callbackAnnotation.getSimpleName() + "' in '"
+ listenerClassDetails.getClassName()
+ "' must return void and accept one argument: " + methodDetails );
}
builders.computeIfAbsent( methodDetails.getArgumentTypes().get( 0 ),
ignored -> new TargetedLifecycleEventHandlerBuilder() )
.setCallbackMethod( callbackType, methodDetails );
}
}
private static class TargetedLifecycleEventHandlerBuilder {
private final EnumMap<CallbackType, MethodDetails> callbackMethods = new EnumMap<>( CallbackType.class );
private TargetedLifecycleEventHandlerBuilder() {
}
private void setCallbackMethod(CallbackType callbackType, MethodDetails method) {View on GitHub (pinned to fad1729dce)
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
Example fix
// before
@PreUpdate
boolean beforeUpdate(Item item, EntityManager em) { return item.isValid(); }
// after
@PreUpdate
void beforeUpdate(Item item) {
if ( !item.isValid() ) throw new IllegalStateException("invalid item");
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: validate every @Pre*/@Post* method in listener classes
for (Class<?> listener : listeners) {
for (java.lang.reflect.Method m : listener.getDeclaredMethods()) {
boolean isCallback = java.util.Arrays.stream(m.getAnnotations())
.anyMatch(a -> a.annotationType().getName().startsWith("jakarta.persistence."));
if (isCallback && !isValidCallback(m))
throw new IllegalStateException("Bad callback signature: " + m);
}
} Type guard
// Narrows a reflection Method to a valid JPA entity-listener callback
static boolean isValidCallback(java.lang.reflect.Method m) {
return m.getReturnType() == void.class && m.getParameterCount() == 1;
} Try / catch
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
Prevention
- 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
When it happens
Trigger: 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.
Common situations: Porting boolean 'validate' style hooks from other frameworks; adding context parameters (EntityManager, audit context) to callbacks; converting interceptors to JPA listeners without adjusting signatures.
Related errors
- Mapping for entity listener specified no callback methods: %
- You can only annotate one callback method per callback type
- Duplicate named query '%s'
- Duplicate named stored procedure '{}'
- Duplicate SQL result set mapping '{}'
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/3b98ceb90d40b33e.
Report an issue: GitHub.