hibernate/hibernate-orm · error · AnnotationException

Class '<className>' is not the root class of an entity inher

Error message

Class '<className>' is not the root class of an entity inheritance hierarchy and may not be annotated '@DiscriminatorOptions'

What it means

DiscriminatorOptionsBinder's type-level bind only executes for RootClass mappings. Annotating any non-root PersistentClass — i.e., an entity subclass in an inheritance hierarchy — falls into the else branch and throws an AnnotationException, because discriminator behavior is defined once on the hierarchy root.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/binder/internal/DiscriminatorOptionsBinder.java:34

 * Handles {@link DiscriminatorOptions} annotations.
 *
 * @author Gavin King
 *
 * @since 6.5
 */
public class DiscriminatorOptionsBinder implements TypeBinder<DiscriminatorOptions> {
	@Override
	public void bind(DiscriminatorOptions options, MetadataBuildingContext context, PersistentClass persistentClass) {
		if ( persistentClass instanceof RootClass rootClass ) {
			if ( !rootClass.hasDiscriminator() ) {
				throw new AnnotationException( "Root entity '" + rootClass.getEntityName()
						+ "' is annotated '@DiscriminatorOptions' but has no discriminator column" );
			}
			rootClass.setForceDiscriminator( options.force() );
			rootClass.setDiscriminatorInsertable( options.insert() );
		}
		else {
			throw new AnnotationException("Class '" + persistentClass.getClassName()
					+ "' is not the root class of an entity inheritance hierarchy and may not be annotated '@DiscriminatorOptions'");
		}
	}

	@Override
	public void bind(DiscriminatorOptions options, MetadataBuildingContext context, Component embeddableClass) {
		throw new AnnotationException("Class '" + embeddableClass.getComponentClassName()
				+ "' is an '@Embeddable' type and may not be annotated '@DiscriminatorOptions'");
	}
}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Move @DiscriminatorOptions to the root @Entity of the hierarchy.
  2. Verify the root also has a discriminator column (@DiscriminatorColumn), otherwise the companion 'no discriminator column' error follows.
  3. Delete the annotation if it was unintentional.

Example fix

// before
@Entity
@DiscriminatorOptions(force = true)
public class CreditCard extends BillingDetails { ... }

// after
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorOptions(force = true) // on the root only
public abstract class BillingDetails { ... }

@Entity
public class CreditCard extends BillingDetails { ... }
Defensive patterns

Strategy: validation

Validate before calling

if (cls.isAnnotationPresent(org.hibernate.annotations.DiscriminatorOptions.class)) {
    for (Class<?> s = cls.getSuperclass(); s != null && s != Object.class; s = s.getSuperclass()) {
        if (s.isAnnotationPresent(jakarta.persistence.Entity.class)) {
            throw new IllegalStateException("@DiscriminatorOptions on non-root " + cls.getName());
        }
    }
}

Try / catch

Catch org.hibernate.AnnotationException during bootstrap; the message names the class. Abort startup.

Prevention

When it happens

Trigger: An entity class that extends another entity (a Subclass mapping) is annotated @DiscriminatorOptions; the binder receives the subclass mapping instead of RootClass and rejects it.

Common situations: Per-subclass annotations added 'to be safe'; entity templates that carry the annotation being used for subclasses; refactoring that moves root-level annotations down the hierarchy.

Related errors


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