hibernate/hibernate-orm · error · AnnotationException
Type '{}' is annotated both '@Entity' and '@MappedSuperclass
Error message
Type '{}' is annotated both '@Entity' and '@MappedSuperclass' What it means
The same type carries both '@Entity' and '@MappedSuperclass'. These are mutually exclusive roles: an entity is a mapped, instantiated persistent type, while a mapped superclass only contributes state/mapping to subclasses. Historically this combination produced a NullPointerException deep inside binding, so Hibernate now fails fast with this clear message.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java:287
if ( context.getMetadataCollector().getClassType( classDetails ) == ENTITY ) {
bindEntityClass( classDetails, inheritanceStatePerClass, context );
}
}
private static void handleImport(ClassDetails annotatedClass, MetadataBuildingContext context) {
if ( annotatedClass.hasDirectAnnotationUsage( Imported.class ) ) {
final String qualifiedName = annotatedClass.getName();
final String name = unqualify( qualifiedName );
final String rename = annotatedClass.getDirectAnnotationUsage( Imported.class ).rename();
context.getMetadataCollector().addImport( rename.isBlank() ? name : rename, qualifiedName );
}
}
private static void detectMappedSuperclassProblems(ClassDetails annotatedClass) {
if ( isMappedSuperclass( annotatedClass ) ) {
// @Entity and @MappedSuperclass on the same class leads to NPE down the road
if ( isEntity( annotatedClass ) ) {
throw new AnnotationException( "Type '" + annotatedClass.getName()
+ "' is annotated both '@Entity' and '@MappedSuperclass'" );
}
if ( annotatedClass.hasDirectAnnotationUsage( Table.class ) ) {
throw new AnnotationException( "Mapped superclass '" + annotatedClass.getName()
+ "' may not specify a '@Table'" );
}
if ( annotatedClass.hasDirectAnnotationUsage( Inheritance.class ) ) {
throw new AnnotationException( "Mapped superclass '" + annotatedClass.getName()
+ "' may not specify an '@Inheritance' mapping strategy" );
}
}
}
private static void bindTypeDescriptorRegistrations(
AnnotationTarget annotatedElement,
MetadataBuildingContext context) {
final var managedBeanRegistry = context.getBootstrapContext().getManagedBeanRegistry();
final var sourceModelContext = modelsContext( context );View on GitHub (pinned to fad1729dce)
Solutions
- If the class should be a mapped superclass: remove '@Entity' (and any '@Table') so it only persists through subclasses.
- If the class should be a concrete entity: remove '@MappedSuperclass' (its state will map to its own table).
- For shared state across entities, keep '@MappedSuperclass' on an abstract base and put '@Entity' on each leaf.
Example fix
// before
@Entity // both present
@MappedSuperclass // -> AnnotationException
public abstract class BaseEntity { @Id Long id; }
// after
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue
Long id;
}
@Entity
class Customer extends BaseEntity { String name; } Defensive patterns
Strategy: validation
Validate before calling
// Guard: a type may not be both @Entity and @MappedSuperclass
if (cls.isAnnotationPresent(MappedSuperclass.class)
&& cls.isAnnotationPresent(Entity.class)) {
throw new IllegalStateException(
cls.getName() + " is annotated both @Entity and @MappedSuperclass");
} Try / catch
try {
Metadata md = sources.buildMetadata();
} catch (AnnotationException e) {
// 'annotated both @Entity and @MappedSuperclass' -> keep exactly one role
throw newConfigurationException("Conflicting type-level annotations", e);
} Prevention
- Keep base classes '@MappedSuperclass' and abstract; entities stay leaf-level '@Entity'.
- When promoting an entity to a base class, remove @Entity AND @Table in the same change.
- Lint persistent classes in CI: reject types carrying both annotations.
When it happens
Trigger: A class annotated '@Entity' (or otherwise processed as an entity) that also has a direct '@MappedSuperclass' annotation — often when someone converts an entity into a base class and leaves '@Entity' behind, or generates mappings that stamp both on.
Common situations: Refactoring an existing entity into a reusable superclass; annotation generators/processors emitting both; copy-paste of a template class that already had @MappedSuperclass.
Related errors
- Mapped superclass '{}' may not specify a '@Table'
- Mapped superclass '{}' may not specify an '@Inheritance' map
- Class '<componentClassName>' is an '@Embeddable' type and ma
- Property '<propertyName>' may not be annotated '@BatchSize'
- One to many association '<propertyName>' was annotated '@Col
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/d6c2507268c590a8.
Report an issue: GitHub.