Skip to content

Jackson2HashMapper breaks when using GraalVM - NotReadablePropertyException #2838

Closed
@daniel-naegele

Description

@daniel-naegele

When I run on spring boot version 3.2.0, spring-boot-starter-data-redis and Java 21 GraalVM the following code:

ObjectRecord<String, CustomObject> record = StreamRecords.newRecord()
                .in("stream-key")
                .ofObject(customObject);
var streamOps = redisTemplate.opsForStream(new Jackson2HashMapper(true));
streamOps.add(record);

with

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CustomObject implements Serializable {

    @NotNull
    private String someText;
    @NotNull
    private long timestamp;
}

I get a NotReadablePropertyException. Logs:

org.springframework.beans.NotReadablePropertyException: Invalid property '_value' of bean class [com.fasterxml.jackson.databind.node.TextNode]: Could not find field for property during fallback access
        at org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper.getPropertyValue(DirectFieldAccessFallbackBeanWrapper.java:55) ~[na:na]
        at org.springframework.data.redis.hash.Jackson2HashMapper.flattenElement(Jackson2HashMapper.java:440) ~[na:na]
        at org.springframework.data.redis.hash.Jackson2HashMapper.doFlatten(Jackson2HashMapper.java:376) ~[na:na]
        at org.springframework.data.redis.hash.Jackson2HashMapper.flattenMap(Jackson2HashMapper.java:363) ~[na:na]
        at org.springframework.data.redis.hash.Jackson2HashMapper.toHash(Jackson2HashMapper.java:247) ~[na:na]
        at org.springframework.data.redis.connection.stream.ObjectRecord.toMapRecord(ObjectRecord.java:63) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:3.2.0]
        at org.springframework.data.redis.core.StreamObjectMapper.toMapRecord(StreamObjectMapper.java:116) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:3.2.0]
        at org.springframework.data.redis.core.DefaultStreamOperations.add(DefaultStreamOperations.java:132) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:3.2.0]
        at com.example.packagename.CustomClass.doShit()
        at java.base@21.0.1/java.lang.reflect.Method.invoke(Method.java:580) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:na]
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:352) ~[na:na]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:713) ~[na:na]
        at de.radiohost.nxtradio.ScheduledTasks$$SpringCGLIB$$0.fetchRadioInformation(<generated>) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:na]
        at java.base@21.0.1/java.lang.reflect.Method.invoke(Method.java:580) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:na]
        at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:130) ~[na:na]
        at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$2(ScheduledMethodRunnable.java:124) ~[na:na]
        at io.micrometer.observation.Observation.observe(Observation.java:499) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:1.12.0]
        at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:124) ~[na:na]
        at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:6.1.1]
        at java.base@21.0.1/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) ~[na:na]
        at java.base@21.0.1/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:na]
        at java.base@21.0.1/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) ~[na:na]
        at java.base@21.0.1/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:na]
        at java.base@21.0.1/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[na:na]
        at java.base@21.0.1/java.lang.Thread.runWith(Thread.java:1596) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:na]
        at java.base@21.0.1/java.lang.Thread.run(Thread.java:1583) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:837) ~[de.radiohost.nxtradio.NxtRadioServiceApplication:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:211) ~[na:na]`

I can fix it with proper reflections registration, but I think this should do the module by itself, or not?

public class MyRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // The following did not work
        hints.serialization().registerType(CustomObject.class);
        // This does work
        try {
            Field field = TextNode.class.getDeclaredField("_value");
            hints.reflection().registerField(field);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}

Also I'd have to do this for LongNode and all other primitive Nodes as well I guess.

Metadata

Metadata

Labels

theme: aotAn issue related to Ahead-Of-Time processingtype: bugA general bug

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions