hibernate/hibernate-orm · error · IllegalArgumentException

Unknown callback annotation : {}

Error message

Unknown callback annotation : {}

What it means

Thrown by CallbackType.fromAnnotation() as an IllegalArgumentException when the annotation passed in is not one of the recognized JPA lifecycle callback annotations (PrePersist, PostPersist, PreRemove, PostRemove, PreUpdate, PostUpdate, PostLoad, or Hibernate's PreDelete/PostDelete/PreMerge/PostMerge/PreUpsert variants). It is a defensive guard in the enum's annotation-resolution helper, not a mapping validator.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/jpa/event/spi/CallbackType.java:113

		if ( PreRemove.class.equals( ann ) ) {
			return PRE_REMOVE;
		}
		if ( PostRemove.class.equals( ann ) ) {
			return POST_REMOVE;
		}
		if ( PreDelete.class.equals( ann ) ) {
			return PRE_DELETE;
		}
		if ( PostDelete.class.equals( ann ) ) {
			return POST_DELETE;
		}
		if ( PreMerge.class.equals( ann ) ) {
			return PRE_MERGE;
		}
		if ( PostLoad.class.equals( ann ) ) {
			return POST_LOAD;
		}
		throw new IllegalArgumentException( "Unknown callback annotation : " + ann );
	}

	/// The callback annotation type corresponding to this lifecycle callback type.
	public Class<? extends Annotation> getCallbackAnnotation() {
		return switch ( this ) {
			case PRE_PERSIST -> PrePersist.class;
			case PRE_UPDATE -> PreUpdate.class;
			case PRE_REMOVE -> PreRemove.class;
			case PRE_INSERT -> PreInsert.class;
			case PRE_DELETE -> PreDelete.class;
			case PRE_MERGE -> PreMerge.class;
			case PRE_UPSERT -> PreUpsert.class;
			case POST_PERSIST -> PostPersist.class;
			case POST_UPDATE -> PostUpdate.class;
			case POST_REMOVE -> PostRemove.class;
			case POST_INSERT -> PostInsert.class;
			case POST_DELETE -> PostDelete.class;
			case POST_UPSERT -> PostUpsert.class;

View on GitHub (pinned to fad1729dce)

Solutions

  1. Filter to known callback annotations before calling fromAnnotation() - check against CallbackType values or the annotation set it documents.
  2. Make sure your entities use jakarta.persistence.* lifecycle annotations, not the legacy javax.persistence.* ones.
  3. If extending Hibernate's callback resolution, handle unknown annotations explicitly instead of delegating them.

Example fix

// before
for (var ann : method.getAnnotations()) {
    CallbackType t = CallbackType.fromAnnotation(ann.annotationType()); // throws on @Transactional
}

// after
for (var ann : method.getAnnotations()) {
    Optional<CallbackType> t = CallbackType.resolveFrom(ann.annotationType());
    t.ifPresent(type -> register(type, method));
}
Defensive patterns

Strategy: type-guard

Type guard

boolean isCallbackAnnotation(Class<? extends Annotation> a) {
    return java.lang.reflect.Annotation.class.isAssignableFrom(a)
        && (a.getName().startsWith("jakarta.persistence.") || a.getName().startsWith("org.hibernate.annotations."))
        && java.util.Set.of("PrePersist","PostPersist","PreRemove","PostRemove","PreUpdate","PostUpdate","PostLoad").contains(a.getSimpleName());
}

Prevention

When it happens

Trigger: Calling CallbackType.fromAnnotation() (or an integration that delegates to it) with a non-callback annotation - e.g. a custom audit annotation, @Entity, or a jakarta vs javax variant of a lifecycle annotation mixed on the classpath.

Common situations: Integrator/enhancement code iterating annotations on entity listeners and forwarding every annotation to this helper; applications stuck with javax.persistence annotations while Hibernate expects jakarta.persistence ones; custom annotation processors that pass through every field/method annotation.

Related errors


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