`PersistenceProvider.fromEntityManagerFactory(…)` throws `NullPointerException` using Hotswap Agent
This happens on startup of a test application when moving from Spring Boot 3.5.1 to Spring Boot 4.0.0-SNAPSHOT (and thus also spring-data-jpa 4.0.0-SNAPSHOT). It only happens when running with Hotswap Agent that apparently creates a proxy of some instances.
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository' defined in test.vaadin.copilot.flow.testviews.service.UserRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: null
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1813) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:604) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:526) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1659) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1606) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
... 22 common frames omitted
Caused by: java.lang.reflect.UndeclaredThrowableException: null
at jdk.proxy2/jdk.proxy2.$Proxy179.unwrap(Unknown Source) ~[na:na]
at org.springframework.data.jpa.provider.PersistenceProvider.fromEntityManagerFactory(PersistenceProvider.java:298) ~[spring-data-jpa-4.0.0-20250618.075653-307.jar:4.0.0-SNAPSHOT]
at org.springframework.data.jpa.provider.PersistenceProvider.fromEntityManager(PersistenceProvider.java:278) ~[spring-data-jpa-4.0.0-20250618.075653-307.jar:4.0.0-SNAPSHOT]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.<init>(JpaRepositoryFactory.java:94) ~[spring-data-jpa-4.0.0-20250618.075653-307.jar:4.0.0-SNAPSHOT]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.createRepositoryFactory(JpaRepositoryFactoryBean.java:191) ~[spring-data-jpa-4.0.0-20250618.075653-307.jar:4.0.0-SNAPSHOT]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.doCreateRepositoryFactory(JpaRepositoryFactoryBean.java:183) ~[spring-data-jpa-4.0.0-20250618.075653-307.jar:4.0.0-SNAPSHOT]
at org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport.createRepositoryFactory(TransactionalRepositoryFactoryBeanSupport.java:81) ~[spring-data-commons-4.0.0-20250618.074033-267.jar:4.0.0-SNAPSHOT]
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:320) ~[spring-data-commons-4.0.0-20250618.074033-267.jar:4.0.0-SNAPSHOT]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:212) ~[spring-data-jpa-4.0.0-20250618.075653-307.jar:4.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1860) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1809) ~[spring-beans-7.0.0-20250623.173043-653.jar:7.0.0-SNAPSHOT]
... 33 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:115) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.hotswap.agent.plugin.hibernate_jakarta.proxy.EntityManagerFactoryProxy$1.invoke(EntityManagerFactoryProxy.java:206) ~[hotswap-agent.jar:2.0.2]
... 44 common frames omitted
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.Class.isInstance(Object)" because "type" is null
at org.hibernate.internal.SessionFactoryImpl.unwrap(SessionFactoryImpl.java:894) ~[hibernate-core-7.0.2.Final.jar:7.0.2.Final]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
The problematic code in Spring Data JPA looks like
if (Proxy.isProxyClass(unwrapped.getClass())) {
unwrapped = unwrapped.unwrap(null);
apparently introduced here https://github.com/spring-projects/spring-data-jpa/commit/98e801cfd5f56a933cc96c26144b7f3d0244627b#diff-5dfadde09c0e52ca72301ee3409505874a939a326b7e6dcb1230ec295847573aR298
which causes an NPE in this case because unwrapped.unwrap calls unwrap in org.hibernate.internal.SessionFactoryImpl: https://github.com/hibernate/hibernate-orm/blob/74db5ed1bc2aa73c8fc4e66d35a9658b47f8be56/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java#L894
This obviously causes an NPE
Is it perhaps supposed to be unwrapped.unwrap(EntityManagerFactory.class)?
Apparently not that simple as there is this code in AbstractEntityManagerFactoryBean
case "unwrap":
Class<?> targetClass = (Class)args[0];
if (targetClass == null) {
return this.entityManagerFactoryBean.getNativeEntityManagerFactory();
} else if (targetClass.isInstance(proxy)) {
return proxy;
}
and that would cause an infinite loop as unwrap here with a target class of EntityManagerFactory would just return the proxy again and again
Thanks for looking into it. There's no consistent behavior we could rely on when unwrapping EntityManagerFactory. Using null as interface type is indeed problematic as Hibernate and EclipseLink do not expect null. However, having a JDK proxy allows us to obtain an invocation handler. We could check if the invocation handler comes from Spring and if so, we call unwrap(null), otherwise, unwrap(EntityManagerFactory.class).
FTR, version 3.5.1 if affected as well.
That's fixed now. Our CI has published new snapshots, care to test against 3.5.2-SNAPSHOT respective 4.0.0-SNAPSHOT, available from https://repos.spring.io/snapshot/?
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
Nice! Seems to work now with the new snapshot
I am getting the same problem with 3.5.1, but not with 3.5.0; seems like a regression there.
3.5.2-SNAPSHOT works.