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

  1. Change the method to return void and accept exactly one parameter typed as the entity (or Object): @PreUpdate void onUpdate(Item item)
  2. Obtain any extra context inside the method body (injected beans, static helpers) rather than as parameters
  3. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/3b98ceb90d40b33e. Report an issue: GitHub.