{"record":{"id":"a61282774dcda4a5","repo":"hibernate/hibernate-orm","slug":"class-classname-is-not-the-root-class-of-an-en","errorCode":null,"errorMessage":"Class '<className>' is not the root class of an entity inheritance hierarchy and may not be annotated '@DiscriminatorOptions'","messagePattern":"Class '<className>' is not the root class of an entity inheritance hierarchy and may not be annotated '@DiscriminatorOptions'","errorType":"exception","errorClass":"AnnotationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/binder/internal/DiscriminatorOptionsBinder.java","lineNumber":34,"sourceCode":" * Handles {@link DiscriminatorOptions} annotations.\n *\n * @author Gavin King\n *\n * @since 6.5\n */\npublic class DiscriminatorOptionsBinder implements TypeBinder<DiscriminatorOptions> {\n\t@Override\n\tpublic void bind(DiscriminatorOptions options, MetadataBuildingContext context, PersistentClass persistentClass) {\n\t\tif ( persistentClass instanceof RootClass rootClass ) {\n\t\t\tif ( !rootClass.hasDiscriminator() ) {\n\t\t\t\tthrow new AnnotationException( \"Root entity '\" + rootClass.getEntityName()\n\t\t\t\t\t\t+ \"' is annotated '@DiscriminatorOptions' but has no discriminator column\" );\n\t\t\t}\n\t\t\trootClass.setForceDiscriminator( options.force() );\n\t\t\trootClass.setDiscriminatorInsertable( options.insert() );\n\t\t}\n\t\telse {\n\t\t\tthrow new AnnotationException(\"Class '\" + persistentClass.getClassName()\n\t\t\t\t\t+ \"' is not the root class of an entity inheritance hierarchy and may not be annotated '@DiscriminatorOptions'\");\n\t\t}\n\t}\n\n\t@Override\n\tpublic void bind(DiscriminatorOptions options, MetadataBuildingContext context, Component embeddableClass) {\n\t\tthrow new AnnotationException(\"Class '\" + embeddableClass.getComponentClassName()\n\t\t\t\t+ \"' is an '@Embeddable' type and may not be annotated '@DiscriminatorOptions'\");\n\t}\n}\n","sourceCodeStart":16,"sourceCodeEnd":45,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/binder/internal/DiscriminatorOptionsBinder.java#L16-L45","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Move @DiscriminatorOptions to the root @Entity of the hierarchy.","Verify the root also has a discriminator column (@DiscriminatorColumn), otherwise the companion 'no discriminator column' error follows.","Delete the annotation if it was unintentional."],"exampleFix":"// before\n@Entity\n@DiscriminatorOptions(force = true)\npublic class CreditCard extends BillingDetails { ... }\n\n// after\n@Entity\n@Inheritance(strategy = InheritanceType.SINGLE_TABLE)\n@DiscriminatorOptions(force = true) // on the root only\npublic abstract class BillingDetails { ... }\n\n@Entity\npublic class CreditCard extends BillingDetails { ... }","handlingStrategy":"validation","validationCode":"if (cls.isAnnotationPresent(org.hibernate.annotations.DiscriminatorOptions.class)) {\n    for (Class<?> s = cls.getSuperclass(); s != null && s != Object.class; s = s.getSuperclass()) {\n        if (s.isAnnotationPresent(jakarta.persistence.Entity.class)) {\n            throw new IllegalStateException(\"@DiscriminatorOptions on non-root \" + cls.getName());\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"Catch org.hibernate.AnnotationException during bootstrap; the message names the class. Abort startup.","preventionTips":["Place inheritance-level annotations only on the hierarchy root.","Avoid per-subclass annotation templates carrying root-level options.","Lint entity hierarchies during build."],"tags":["hibernate","jpa","annotations","inheritance","discriminator","orm-mapping"],"backgroundTag":"jpa-annotation-misplacement","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}