Environment
-
Hibernate ORM Version: 7.2.7.Final through 7.2.9.Final (Regression from 7.1.x)
-
JDK Version: 25
-
Framework: Spring Boot 4.0.5 (Kotlin 2.3.0)
Description
When calling EntityManager.merge() on an entity that contains a polymorphic @Embeddable (an embeddable hierarchy using @DiscriminatorColumn and @DiscriminatorValue), Hibernate 7.2 throws a ClassCastException: class java.lang.Class cannot be cast to class java.lang.String.
This is a regression introduced in the 7.2.x line. The identical code and operations work perfectly in Hibernate 7.1.8.Final (and 7.1.21.Final).
Steps to Reproduce
-
Create an entity with an
@Embeddedproperty pointing to a polymorphic embeddable hierarchy. -
Define the embeddable hierarchy with
@DiscriminatorColumn(name = "...")and subclasses with@DiscriminatorValue. -
Fetch an existing entity from the database.
-
Modify the entity (or leave it as is) and call
EntityManager.merge()(e.g., via Spring Data JPA’ssave()).
Reproducer Classes (Kotlin):
@Embeddable
@DiscriminatorColumn(name = "publishing_mode")
sealed class Publishing(
@Enumerated(EnumType.STRING)
@Column(name = "publishing_mode", insertable = false, updatable = false, nullable = false)
val mode: PublishingMode,
) {
@Embeddable
@DiscriminatorValue("SAMPLE")
data object Sample : Publishing(PublishingMode.SAMPLE)
@Embeddable
@DiscriminatorValue("SAMPLE_VALUE_CHANGED")
data object SampleValueChanged : Publishing(PublishingMode.SAMPLE_VALUE_CHANGED)
@Embeddable
@DiscriminatorValue("INTERVAL")
data class Interval(
@Column(name = "publishing_interval")
val interval: Duration,
) : Publishing(PublishingMode.INTERVAL)
}
@Entity
class DatapointTemplate {
@Id val id: UUID
@Embedded var publishing: Publishing? = null
// ...
}
Expected Behavior
The entity should merge successfully, updating the database as it did in Hibernate 7.1.x.
Actual Behavior
The merge operation fails internally with the following stack trace:
java.lang.ClassCastException: class java.lang.Class cannot be cast to class java.lang.String (java.lang.Class and java.lang.String are in module java.base of loader 'bootstrap')
at org.hibernate.type.descriptor.java.StringJavaType.cast(StringJavaType.java:53)
at org.hibernate.type.descriptor.java.StringJavaType.cast(StringJavaType.java:26)
at org.hibernate.type.AbstractStandardBasicType.replace(AbstractStandardBasicType.java:292)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:85)
at org.hibernate.type.ComponentType.replace(ComponentType.java:537)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:113)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:268)
at org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:198)
at org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:136)
Root Cause Analysis
The issue lies in how Hibernate 7.2 handles the discriminator value internally for polymorphic embeddables during the replace step of a merge.
-
ComponentType.replace()callsgetPropertyValues(original), which delegates toembeddableTypeDescriptor().getValues(component). -
For polymorphic embeddables, this returns an array of values where the discriminator value is represented as an internal
Class<?>object rather than its mapped string value. (Note:ComponentType.resolve()confirms this behavior with the comment:// the discriminator here is the composite class because it gets converted to the domain type when extracted). -
ComponentType.replace()then passes this array toTypeHelper.replace(), which iterates over thepropertyTypes. -
The discriminator property type expects a
String(backed byStringJavaType). -
When
AbstractStandardBasicType.replace()attempts to process the discriminator, it routes toStringJavaType.cast(), which executes a raw(String) valuecast. -
Since the value is a
Class<?>object and not aString, aClassCastExceptionis thrown.
The replace() code path does not account for the internal Class<?> representation of embeddable discriminators introduced in recent versions.
Workaround
Downgrading/pinning Hibernate ORM to 7.1.21.Final entirely resolves the issue.