Cannot be cast to class org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer

Hey, i’m playing with hibernate 6.6.15.Final and facing a weird issue that I believe was introduced recently as it works 6.6.10.Final(I think).
The issue is a bit hard to reproduce as the class load(or better to say hibernate parsing I believe) order is involved.
There are 2 entities A and B. they are in the different packages. and let’s B extends A ( or A extends B depending what will be parsed first)
both entities has protected fields and public getters/setters:
for example:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class A {
    @Id
    protected long id;
    protected String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import org.hibernate.bugs.A;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class B extends A{
    @Id
    protected long id;
    protected String bname;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getBname() {
        return bname;
    }

    public void setBname(String bname) {
        this.bname = bname;
    }
}

Now start the test:

	// Entities are auto-discovered, so just add them anywhere on class-path
	// Add your tests, using standard JUnit.
	@Test
	void hhh123Test() throws Exception {
		EntityManager entityManager = entityManagerFactory.createEntityManager();
		entityManager.getTransaction().begin();
		// Do stuff...
		org.hibernate.bugs.A a = entityManager.find(A.class, 1L);
		entityManager.getTransaction().commit();
		entityManager.close();
	}

and see exception:

	Suppressed: java.lang.NullPointerException: Cannot invoke "jakarta.persistence.EntityManagerFactory.close()" because "this.entityManagerFactory" is null
		at org.hibernate.bugs.JPAUnitTestCase.destroy(JPAUnitTestCase.java:25)
		... 3 more
Caused by: org.hibernate.HibernateException: java.lang.ClassCastException: class org.hibernate.bugs.A$HibernateAccessOptimizer5name cannot be cast to class org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer (org.hibernate.bugs.A$HibernateAccessOptimizer5name and org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer are in unnamed module of loader 'app')
	at org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl.getReflectionOptimizer(BytecodeProviderImpl.java:280)
	at org.hibernate.metamodel.internal.EntityRepresentationStrategyPojoStandard.resolveReflectionOptimizer(EntityRepresentationStrategyPojoStandard.java:325)
	at org.hibernate.metamodel.internal.EntityRepresentationStrategyPojoStandard.<init>(EntityRepresentationStrategyPojoStandard.java:154)
	at org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard.resolveStrategy(ManagedTypeRepresentationResolverStandard.java:62)
	at org.hibernate.persister.entity.AbstractEntityPersister.<init>(AbstractEntityPersister.java:562)
	at org.hibernate.persister.entity.JoinedSubclassEntityPersister.<init>(JoinedSubclassEntityPersister.java:187)
	at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)
	at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:94)
	at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:77)
	at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.processBootEntities(MappingMetamodelImpl.java:250)
	at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.finishInitialization(MappingMetamodelImpl.java:184)
	at org.hibernate.internal.SessionFactoryImpl.initializeMappingModel(SessionFactoryImpl.java:373)
	at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:302)
	at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:463)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1517)
	... 7 more
Caused by: java.lang.ClassCastException: class org.hibernate.bugs.A$HibernateAccessOptimizer5name cannot be cast to class org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer (org.hibernate.bugs.A$HibernateAccessOptimizer5name and org.hibernate.bytecode.spi.ReflectionOptimizer$AccessOptimizer are in unnamed module of loader 'app')
	at org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl.getReflectionOptimizer(BytecodeProviderImpl.java:276)
	... 23 more

I did a bit of debugging:
So it first loads/parse the “child” entity, in my specific case this is B. The code in org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl#getReflectionOptimizer(java.lang.Class<?>, java.util.Map<java.lang.String,org.hibernate.property.access.spi.PropertyAccess>) finds a superclass - which is A (A and B are in the different packages)
org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl#determineAccessOptimizerSuperClass
and loads/optimizes A

byteBuddyState.load(
					foreignPackageClassInfo.clazz,
					className,
					(byteBuddy, namingStrategy) -> {
						DynamicType.Builder<?> builder = byteBuddy.with( namingStrategy ).subclass( newSuperClass );

so after processing for B class is finished it will process A and in org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl#getReflectionOptimizer(java.lang.Class<?>, java.util.Map<java.lang.String,org.hibernate.property.access.spi.PropertyAccess>)
it will not optimize it with(probably because it was already loaded/optimized as a superclass?):

byteBuddyState.load( clazz, className, (byteBuddy, namingStrategy) -> byteBuddy
					.with( namingStrategy )
					.subclass( superClass )
					.implement( ReflectionOptimizer.AccessOptimizer.class )

and so attempt to cast to ReflectionOptimizer.AccessOptimizer.class fails.

I fixed it by setting @Transient on getId of the base class and making id property as private. Don’t ask me why. Also to note we have enabled bytecode enhancing. This problem appeared exactly after enabling enhancing.

Would you please be so kind and try to reproduce this in a sample application? Even if you can’t reproduce this consistently (class loading order being the culprit), having a sample for analysis would be a huge help. We have a test case template for bytecode enhancement tests here: hibernate-test-case-templates/orm/hibernate-orm-6/src/test/java/org/hibernate/bugs/QuarkusLikeORMUnitTestCase.java at main · hibernate/hibernate-test-case-templates · GitHub

We are currently trying to track down what is going on here exactly, but couldn’t reproduce it yet.

here it is reproducer for class cast https://discourse.hibernate.org/t/cannot-be-cast-to-cl… by oleksiimiroshnyk · Pull Request #514 · hibernate/hibernate-test-case-templates · GitHub

1 Like

I’m having a similar issue and it appears, in my case at least, to have started in 6.6.12.Final. Hopefully that helps narrow things down.

This sounds exactly like Jira. We have a work-in-progress fix for this issue here: HHH-19372 HHH-19369 Issues with access optimizer and inheritance by dreab8 · Pull Request #10075 · hibernate/hibernate-orm · GitHub.

Thanks, interesting I wasn’t able to find it by keywords
Will be waiting for the fix.
I can’t find version where it is planned to be fixed in.