hibernate/hibernate-orm · error · MappingException

discriminator mapping required for single table polymorphic

Error message

discriminator mapping required for single table polymorphic persistence

What it means

SingleTableEntityPersister requires every polymorphic SINGLE_TABLE mapping to have a discriminator, because all subclasses share one physical table and rows can only be told apart by the discriminator value. At SessionFactory boot, when persistentClass.isPolymorphic() is true but getDiscriminator() returns null, a MappingException is thrown. This is a mapping configuration error, not a runtime data problem.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java:211

			final var columns = join.getKey().getColumns();
			for ( int i = 0; i < columns.size(); i++ ) {
				keyCols[i] = columns.get( i ).getQuotedName( dialect );
			}
			joinKeyColumns.add( keyCols );
		}

		subclassTableNameClosure = toStringArray( subclassTables );
		subclassTableKeyColumnClosure = to2DStringArray( joinKeyColumns );
		isClassOrSuperclassTable = toBooleanArray( isConcretes );
		isClassOrSuperclassJoin = toBooleanArray( isClassOrSuperclassJoins );
		isNullableSubclassTable = toBooleanArray( isNullables );

		// DISCRIMINATOR

		if ( persistentClass.isPolymorphic() ) {
			final var discriminator = persistentClass.getDiscriminator();
			if ( discriminator == null ) {
				throw new MappingException( "discriminator mapping required for single table polymorphic persistence" );
			}
			forceDiscriminator = persistentClass.isForceDiscriminator();
			final var selectable = discriminator.getSelectables().get( 0 );
			discriminatorType = DiscriminatorHelper.getDiscriminatorType( persistentClass );
			discriminatorValue = DiscriminatorHelper.getDiscriminatorValue( persistentClass );
			discriminatorSQLValue = DiscriminatorHelper.getDiscriminatorSQLValue( persistentClass, dialect );
			discriminatorInsertable = isDiscriminatorInsertable( persistentClass );
			if ( selectable instanceof Formula formula ) {
				discriminatorFormulaTemplate = formula.getTemplate( dialect, typeConfiguration );
				discriminatorColumnName = null;
				discriminatorColumnReaders = null;
//				discriminatorColumnReaderTemplate = null;
				discriminatorAlias = "clazz_";
			}
			else if ( selectable instanceof Column column ) {
				discriminatorColumnName = column.getQuotedName( dialect );
				discriminatorColumnReaders = column.getReadExpr( dialect );
//				discriminatorColumnReaderTemplate = column.getTemplate( dialect, typeConfiguration );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Add a discriminator to the root: @DiscriminatorColumn (JPA) or <discriminator column="dtype"/> (hbm.xml)
  2. Give each class in the hierarchy an explicit @DiscriminatorValue / discriminator-value
  3. If sharing one table is not required, switch the hierarchy to JOINED or TABLE_PER_CLASS
  4. When building mappings programmatically, define the discriminator before subclasses are processed

Example fix

// before: polymorphic single-table root, no discriminator
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class BillingDetails { ... }

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

// after (hbm equivalent: <discriminator column="dtype"/>)
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
public class BillingDetails { ... }

@Entity
@DiscriminatorValue("CC")
public class CreditCard extends BillingDetails { ... }
Defensive patterns

Strategy: validation

Validate before calling

// after Metadata build, before SessionFactory
for (PersistentClass pc : metadata.getEntityBindings()) {
    if (pc instanceof RootClass root && root.isPolymorphic() && root.getDiscriminator() == null) {
        throw new IllegalStateException("Polymorphic root without discriminator: " + root.getEntityName());
    }
}

Try / catch

try { sessionFactory = cfg.buildSessionFactory(); } catch (MappingException e) { if (e.getMessage().contains("discriminator mapping required")) { /* add @DiscriminatorColumn / <discriminator> to the root */ } throw e; }

Prevention

When it happens

Trigger: hbm.xml <class> with <subclass> elements but no <discriminator> element; programmatic mapping that adds subclasses to a root without defining a discriminator; mappings where discriminator configuration was lost during generation or migration.

Common situations: Legacy hbm.xml mappings brought forward from very old Hibernate versions; tooling that generates mappings without discriminators; mixing JPA annotations and hbm fragments for the same hierarchy.

Related errors


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