hibernate/hibernate-orm · error · WrongClassException

Expected object of type `%s`, but found `%s`; discriminator

Error message

Expected object of type `%s`, but found `%s`; discriminator = %s

What it means

When an entity initializer reads a discriminator to pick the concrete persister (resolveConcreteDescriptor), it calls discriminatorMapping.resolveDiscriminatorValue(discriminator) and then checks indicatedEntity.isTypeOrSuperType(entityDescriptor). If the class the discriminator indicates is not the expected type or a subtype of it, Hibernate throws WrongClassException('Expected object of type X, but found Y; discriminator = <value>'). The row claims an inheritance type that does not fit where the row was found.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java:1054

		}
		else {
			assert entityDescriptor.hasSubclasses()
					: "Reading a discriminator from a result set should only happen if the entity has subclasses";
			final var discriminatorMapping = entityDescriptor.getDiscriminatorMapping();
			assert discriminatorMapping != null;
			final Object discriminator = discriminatorAssembler.extractRawValue( rowProcessingState );
			final var discriminatorDetails = discriminatorMapping.resolveDiscriminatorValue( discriminator );
			if ( discriminatorDetails == null ) {
				assert discriminator == null : "Discriminator details should only be null for null values";
				return null;
			}
			else {
				final var indicatedEntity = discriminatorDetails.getIndicatedEntity();
				if ( indicatedEntity.isTypeOrSuperType( entityDescriptor ) ) {
					return indicatedEntity.getEntityPersister();
				}
				else {
					throw new WrongClassException(
							indicatedEntity.getEntityName(),
							null,
							entityDescriptor.getEntityName(),
							discriminator
					);
				}
			}
		}
	}

	protected boolean useEmbeddedIdentifierInstanceAsEntity(EntityInitializerData data) {
		if ( data.canUseEmbeddedIdentifierInstanceAsEntity ) {
			data.concreteDescriptor =
					determineConcreteEntityDescriptor( data.getRowProcessingState(),
							discriminatorAssembler, entityDescriptor );
			return data.concreteDescriptor != null
				&& data.concreteDescriptor.isInstance( data.getRowProcessingState().getEntityId() );
		}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Compare the reported discriminator value with your @DiscriminatorValue mapping and UPDATE the rows to a valid, matching value.
  2. If the row legitimately belongs to a different branch, correct the declared association type to the common superclass of all possible targets.
  3. After renaming entities/discriminator values, run a data migration (UPDATE ... SET dtype = 'NewName' WHERE dtype = 'OldName').
  4. Verify the inheritance mapping (@Inheritance(strategy=...), @DiscriminatorColumn) matches how the data was written.

Example fix

-- before: rows carry a discriminator Hibernate cannot map onto the expected hierarchy
UPDATE document SET dtype = 'CONTRACT' WHERE dtype = 'AGR_CONTRACT';

// before (association declared too narrow for the data present)
@ManyToOne(targetEntity = Contract.class)
private Contract document;

// after (declare the branch the data actually contains, or the common base)
@ManyToOne(targetEntity = LegalDocument.class)
private LegalDocument document;
Defensive patterns

Strategy: validation

Validate before calling

// Compare distinct discriminator values in the DB against the mapped ones
Map<String, Class<?>> allowed = Map.of("DOG", Dog.class, "CAT", Cat.class); // your @DiscriminatorValues
List<Object> distinct = em.createNativeQuery("select distinct dtype from animal").getResultList();
for (Object d : distinct) {
    if (d == null || !allowed.containsKey(d.toString())) {
        throw new IllegalStateException("Unknown discriminator value in animal: " + d);
    }
}

Type guard

// Narrow loaded polymorphic values before use
if (document instanceof Contract c) { ... } // pattern-match the concrete branch

boolean fitsExpectedBranch(Class<?> rowType, Class<?> expected) {
    return expected.isAssignableFrom(rowType);
}

Try / catch

try {
    List<LegalDocument> docs = em.createQuery("select d from LegalDocument d", LegalDocument.class).getResultList();
} catch (WrongClassException e) {
    // e.getExpectedClassName() / e.getActualClassName() / e.getDiscriminator()
    // quarantine the row or widen the declared association type
}

Prevention

When it happens

Trigger: A polymorphic association declared against class A, but the row's discriminator denotes class B outside A's hierarchy branch; discriminator strings in the DB not matching any @DiscriminatorValue (e.g. after renaming entities or discriminator values); manually inserted rows with a wrong discriminator; joined-inheritance data where subclass rows and discriminators disagree.

Common situations: Refactoring or renaming entity names / @DiscriminatorValue strings without migrating existing rows; multi-module apps where one module inserts rows with discriminators another module's mapping does not know; copy-pasted data between environments; adding a new subclass to one deployment while older rows reference a removed branch.

Related errors


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