Hibernate 6.6.0 lazy loading produces reflection exception

Hello,

this weekend our dependency management switched hibernate + gradle plugin version to 6.6.0.Final, from previously 6.5.2.Final. This caused an unexpected reflection error on our production server:

org.springframework.orm.jpa.JpaSystemException: Error accessing field [public ourcorp.jpa.SkillProfile ourcorp.jpa.UserSkillProfile.skillProfile] by reflection for persistent property [ourcorp.jpa.UserSkillProfile#skillProfile] : 
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:341)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:241)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:335)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:160)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
    at jdk.proxy2/jdk.proxy2.$Proxy250.refresh(Unknown Source)
...
Caused by: org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [public ourcorp.jpa.SkillProfile ourcorp.jpa.UserSkillProfile.skillProfile] by reflection for persistent property [ourcorp.jpa.UserSkillProfile#skillProfile] : UserSkillProfilePK(skillProfile=10, person=teamcoord, proficiencyLevel=40)
	at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:52)
	at org.hibernate.metamodel.mapping.internal.AbstractEmbeddableMapping.getValue(AbstractEmbeddableMapping.java:108)
	at org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingInitializer.resolveInstanceSubInitializers(NonAggregatedIdentifierMappingInitializer.java:281)
	at org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingInitializer.resolveInstance(NonAggregatedIdentifierMappingInitializer.java:268)
	at org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingInitializer.resolveInstance(NonAggregatedIdentifierMappingInitializer.java:46)
	at org.hibernate.sql.results.graph.Initializer.resolveInstance(Initializer.java:149)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstance(EntityInitializerImpl.java:882)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstance(EntityInitializerImpl.java:94)
	at org.hibernate.sql.results.graph.Initializer.resolveInstance(Initializer.java:149)
	at org.hibernate.sql.results.graph.collection.internal.BagInitializer.resolveInstanceSubInitializers(BagInitializer.java:131)
	at org.hibernate.sql.results.graph.collection.internal.AbstractImmediateCollectionInitializer.resolveInstance(AbstractImmediateCollectionInitializer.java:358)
	at org.hibernate.sql.results.graph.collection.internal.AbstractImmediateCollectionInitializer.resolveInstance(AbstractImmediateCollectionInitializer.java:42)
	at org.hibernate.sql.results.graph.Initializer.resolveInstance(Initializer.java:149)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstanceSubInitializers(EntityInitializerImpl.java:599)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstance(EntityInitializerImpl.java:888)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstance(EntityInitializerImpl.java:94)
	at org.hibernate.sql.results.graph.Initializer.resolveInstance(Initializer.java:149)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstanceSubInitializers(EntityInitializerImpl.java:599)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveKey(EntityInitializerImpl.java:534)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveKey(EntityInitializerImpl.java:424)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveKey(EntityInitializerImpl.java:94)
	at org.hibernate.sql.results.graph.Initializer.resolveKey(Initializer.java:101)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveKeySubInitializers(EntityInitializerImpl.java:618)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveKey(EntityInitializerImpl.java:498)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveKey(EntityInitializerImpl.java:424)
	at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveKey(EntityInitializerImpl.java:94)
	at org.hibernate.sql.results.internal.StandardRowReader.coordinateInitializers(StandardRowReader.java:235)
	at org.hibernate.sql.results.internal.StandardRowReader.readRow(StandardRowReader.java:141)
	at org.hibernate.sql.results.spi.ListResultsConsumer.readUnique(ListResultsConsumer.java:283)
	at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:195)
	at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:35)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:224)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:102)
	at org.hibernate.sql.exec.spi.JdbcSelectExecutor.executeQuery(JdbcSelectExecutor.java:91)
	at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:165)
	at org.hibernate.loader.ast.internal.SingleIdLoadPlan.load(SingleIdLoadPlan.java:145)
	at org.hibernate.loader.ast.internal.SingleIdEntityLoaderStandardImpl.load(SingleIdEntityLoaderStandardImpl.java:89)
	at org.hibernate.persister.entity.AbstractEntityPersister.doLoad(AbstractEntityPersister.java:3778)
	at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3760)
	at org.hibernate.event.internal.DefaultRefreshEventListener.doRefresh(DefaultRefreshEventListener.java:273)
	at org.hibernate.event.internal.DefaultRefreshEventListener.lambda$refresh$0(DefaultRefreshEventListener.java:199)
	at org.hibernate.engine.spi.LoadQueryInfluencers.fromInternalFetchProfile(LoadQueryInfluencers.java:109)
	at org.hibernate.event.internal.DefaultRefreshEventListener.refresh(DefaultRefreshEventListener.java:197)
	at org.hibernate.event.internal.DefaultRefreshEventListener.refresh(DefaultRefreshEventListener.java:184)
	at org.hibernate.event.internal.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:109)
	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
	at org.hibernate.internal.SessionImpl.fireRefresh(SessionImpl.java:1315)
	at org.hibernate.internal.SessionImpl.refresh(SessionImpl.java:1276)
	at org.hibernate.engine.spi.CascadingActions$3.cascade(CascadingActions.java:139)
	at org.hibernate.engine.spi.CascadingActions$3.cascade(CascadingActions.java:129)
	at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:550)
	at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:472)
	at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:231)
	at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:584)
	at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:514)
	at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:475)
	at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:231)
	at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:170)
	at org.hibernate.event.internal.DefaultRefreshEventListener.refresh(DefaultRefreshEventListener.java:164)
	at org.hibernate.event.internal.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:109)
	at org.hibernate.event.internal.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:55)
	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
	at org.hibernate.internal.SessionImpl.fireRefresh(SessionImpl.java:1295)
	at org.hibernate.internal.SessionImpl.refresh(SessionImpl.java:1246)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364)
	at jdk.proxy2/jdk.proxy2.$Proxy235.refresh(Unknown Source)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:319)
	at jdk.proxy2/jdk.proxy2.$Proxy235.refresh(Unknown Source)
	at ourcorp.jpa.JobRepositoryCustomImpl.refresh(JobRepositoryCustom.kt:203)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:355)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:768)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:768)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:720)
	at ourcorp.jpa.JobRepositoryCustomImpl$$SpringCGLIB$$0.refresh(<generated>)
...	 
Caused by: java.lang.IllegalArgumentException: Can not get ourcorp.jpa.SkillProfile field ourcorp.jpa.UserSkillProfile.skillProfile on ourcorp.jpa.pk.UserSkillProfilePK
	at java.base/jdk.internal.reflect.MethodHandleFieldAccessorImpl.newGetIllegalArgumentException(MethodHandleFieldAccessorImpl.java:86)
	at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.get(MethodHandleObjectFieldAccessorImpl.java:61)
	at java.base/java.lang.reflect.Field.get(Field.java:444)
	at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:48)

this happens during the jpa refresh operation of a loaded entity - after calling a modifying legacy stored procedure for the entity. The fun fact is that the entities complained above are linked through a couple of manyToOnes that are marked as fetch lazy and shouldn’t be a problem in the first place.

So I was then comparing the traced queries between version 6.5.2 and 6.6.0 and the result shows that lazy loading was no longer applied with the refresh operation.

So my first assumption is that either the gradle hibernate enhancer plugin stopped working as expected, or something in JPA query optimizer goes really wrong.

Our codebase uses Kotlin 1.9 sources.

Please advise.

There are a lot of elements at play here, as you’re application is using different components other than Hibernate.

Your best course of action to try and get to a solution is to isolate the query / operations leading to this error and trying to reproduce the error you’re encountering by translating your logic, along with the involved entity mappings, to a plain java + Hibernate application. You can find test case templates in our repository which are a good place to start from.

I can work on a test case, but in the meantime, could someone look at the stack trace and see what possibly has gone wrong at the hibernate reflection code there? And compare the relevant code with the 6.5.2 code, as this is the one keg changed that stopped the machine.

Caused by: java.lang.IllegalArgumentException: Can not get ourcorp.jpa.SkillProfile field ourcorp.jpa.UserSkillProfile.skillProfile on ourcorp.jpa.pk.UserSkillProfilePK
	at java.base/jdk.internal.reflect.MethodHandleFieldAccessorImpl.newGetIllegalArgumentException(MethodHandleFieldAccessorImpl.java:86)
	at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.get(MethodHandleObjectFieldAccessorImpl.java:61)
	at java.base/java.lang.reflect.Field.get(Field.java:444)
	at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:48)

I can provide you the kotlin source for that PK now:

package ourcorp.jpa.pk
import lombok.NoArgsConstructor
import java.io.Serializable

@NoArgsConstructor
data class UserSkillProfilePK(
    var skillProfile: Int,
    var person: String,
    var proficiencyLevel: Int,
) : Serializable

I just looked into the JDK source of that root cause:

It fails with a masked class cast exception there - so whatever hibernate fed into the reflection field getter was not of the right type.