spring-projects/spring-data-commons

`TypeInformation.OBJECT` is `null` when only `ClassTypeInformation.COLLECTION` has been accessed

Closed this issue ยท 3 comments

Hi, spring-boot migration 3.5.3->3.5.4 causes NPE (spring-data-relational 3.5.2)

this happens in
MappingRelationalConverter.writeValue() because
if (TypeInformation.OBJECT != type) { was replaced with if (!TypeInformation.OBJECT.equals(type)) {

I have a rather specific app: This app is used for testing purposes. It allows to start several spring-boot based micro-services in a single jvm.
Because of complex hierarchy TypeInformation <-> ClassTypeInformation and dependencies of static objects
the field TypeInformation.OBJECT may have a null value, which causes npe:

java.lang.NullPointerException: Cannot invoke "Object.equals(Object)" because "org.springframework.data.util.TypeInformation.OBJECT" is null
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter.writeValue(MappingRelationalConverter.java:705)
	at org.springframework.data.jdbc.core.convert.MappingJdbcConverter.writeValue(MappingJdbcConverter.java:214)
	at org.springframework.data.jdbc.core.convert.MappingJdbcConverter.writeJdbcValue(MappingJdbcConverter.java:247)
	at org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery.writeValue(StringBasedJdbcQuery.java:320)
	at org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery.bindParameters(StringBasedJdbcQuery.java:281)
	at org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery.execute(StringBasedJdbcQuery.java:230)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at io.opentelemetry.javaagent.instrumentation.spring.data.v1_8.SpringDataInstrumentationModule$RepositoryInterceptor.invoke(SpringDataInstrumentationModule.java:112)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
	at jdk.proxy6/jdk.proxy6.$Proxy217.getUIDealsStatistic(Unknown Source)
	at foo.baz.service.FooIndex.loadCache(FooIndex.java:61)
	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.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130)
	at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124)
	at io.micrometer.observation.Observation.observe(Observation.java:498)
	at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124)

There is a workaround to fix:
add TypeInformation.of(String.class); as a first statement in main method.

I found a simple option to reproduce the issue:
adding two lines in main method

    public static void main(String[] args) {
        Object ignore = ClassTypeInformation.COLLECTION;
        Object ignore2 = TypeInformation.OBJECT;
        // ...

May if (!type.equals(TypeInformation.OBJECT)) { help?

The object initialization cycle causes visibility issues. TypeInformation is being initialized while its subtype ClassTypeInformation was not yet initialized leading to assigning a null value into TypeInformation static fields.

That's fixed now and new snapshot builds are available from https://repo.spring.io/snapshot.