Hi,I got a problem when my spring-boot program running with native image. It can't serializer java.sql.Date caus class com.fasterxml.jackson.databind.ser.std.SqlDateSerializer not found.It only happend in native image.if my program don't running in native image,it works well.

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Failed to find class `com.fasterxml.jackson.databind.ser.std.SqlDateSerializer` for handling values of type `java.sql.Date`, problem: (java.lang.ClassNotFoundException) com.fasterxml.jackson.databind.ser.std.SqlDateSerializer
        at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:492) ~[v-tools:6.0.10]
        at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:103) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:297) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:194) ~[na:na]
        at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78) ~[na:na]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:136) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[v-tools:6.0.10]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[v-tools:6.0.10]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[v-tools:6.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[v-tools:6.0.10]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[v-tools:6.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[na:na]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[v-tools:10.1.10]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[na:na]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[v-tools:6.0.10]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[v-tools:6.0.10]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[na:na]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[v-tools:6.0.10]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[v-tools:6.0.10]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[na:na]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[v-tools:6.0.10]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[v-tools:6.0.10]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[na:na]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[na:na]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166) ~[na:na]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[na:na]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[v-tools:10.1.10]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[na:na]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[v-tools:10.1.10]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[na:na]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) ~[na:na]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[na:na]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[v-tools:10.1.10]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[na:na]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[na:na]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[v-tools:10.1.10]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[na:na]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[na:na]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[na:na]
        at java.base@17.0.5/java.lang.Thread.run(Thread.java:833) ~[v-tools:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:775) ~[v-tools:na]
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:203) ~[na:na]
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Failed to find class `com.fasterxml.jackson.databind.ser.std.SqlDateSerializer` for handling values of type `java.sql.Date`, problem: (java.lang.ClassNotFoundException) com.fasterxml.jackson.databind.ser.std.SqlDateSerializer (through reference chain: com.github.vince.common.base.Result["data"]->java.util.ArrayList[0]->com.github.vince.domain.schedule.entity.TaskList["tasks"]->java.util.ArrayList[0]->com.github.vince.domain.schedule.entity.Task["taskDate"])
        at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:323) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:780) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[na:na]
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119) ~[na:na]
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79) ~[na:na]
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18) ~[na:na]
        at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:772) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[na:na]
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119) ~[na:na]
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79) ~[na:na]
        at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18) ~[na:na]
        at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:732) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:772) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178) ~[na:na]
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:479) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:318) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1572) ~[na:na]
        at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:1061) ~[v-tools:2.15.2]
        at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:483) ~[v-tools:6.0.10]
        ... 50 common frames omitted
Caused by: java.lang.IllegalStateException: Failed to find class `com.fasterxml.jackson.databind.ser.std.SqlDateSerializer` for handling values of type `java.sql.Date`, problem: (java.lang.ClassNotFoundException) com.fasterxml.jackson.databind.ser.std.SqlDateSerializer
        at com.fasterxml.jackson.databind.ext.OptionalHandlerFactory.instantiate(OptionalHandlerFactory.java:242) ~[na:na]
        at com.fasterxml.jackson.databind.ext.OptionalHandlerFactory.findSerializer(OptionalHandlerFactory.java:153) ~[na:na]
        at com.fasterxml.jackson.databind.ser.BasicSerializerFactory.findOptionalStdSerializer(BasicSerializerFactory.java:496) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.BasicSerializerFactory.findSerializerByPrimaryType(BasicSerializerFactory.java:432) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._createSerializer2(BeanSerializerFactory.java:235) ~[na:na]
        at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:174) ~[na:na]
        at com.fasterxml.jackson.databind.SerializerProvider._createUntypedSerializer(SerializerProvider.java:1503) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1451) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.SerializerProvider.findPrimaryPropertySerializer(SerializerProvider.java:717) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap.findAndAddPrimarySerializer(PropertySerializerMap.java:64) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.BeanPropertyWriter._findAndAddDynamic(BeanPropertyWriter.java:901) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:710) ~[v-tools:2.15.2]
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:772) ~[v-tools:2.15.2]
        ... 68 common frames omitted

Here is the response from my program which running in native image and the struct of the task field.Other types of fields are fine.

{
    "code": 0,
    "data": [
        {
            "status": "NOT_START",
            "tasks": [
                {
                    "id": 1,
                    "parentId": null,
                    "name": "test",
                    "details": null,
                    "taskStatus": 0,
                    "progress": 0
                }
            ]
        }
    ]
}{
    "code": 100,
    "data": "Could not write JSON: Failed to find class `com.fasterxml.jackson.databind.ser.std.SqlDateSerializer` for handling values of type `java.sql.Date`, problem: (java.lang.ClassNotFoundException) com.fasterxml.jackson.databind.ser.std.SqlDateSerializer",
    "msg": "error"
}

Spring Add support for Jackson optional serializers in BindingReflectionHintsRegistrar

What I use

  • Openjdk 17.0.5
  • GraalVM 22.3.0
  • Gradle 8.1.1
  • MacBook Pro (Apple M2 Pro)

And the follow is my gradle.build, did I write something wrong?

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.1'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'org.hibernate.orm' version '6.1.6.Final'
    id 'org.graalvm.buildtools.native' version '0.9.23'
}


group = 'com.github.vince'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

compileJava {
    options.compilerArgs << "-Amapstruct.unmappedTargetPolicy=IGNORE"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'com.h2database:h2'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    implementation 'org.mapstruct:mapstruct:1.5.5.Final'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

hibernate {
    enhancement {
        lazyInitialization true
        dirtyTracking true
        associationManagement true
    }
}

Comment From: wilkinsona

Jackson loads a small number of its less commonly used serializers using reflection. Without some additional metadata, these serializers cannot be loaded in a native image. You could fix your problem by providing some reflection hints that allow SqlDateSerializer to be loaded and instantiated through reflection.

I'll transfer this issue to the Framework team as they may want to consider automatically adding the necessary reflection hints when org.springframework.aot.hint.annotation.RegisterReflectionForBindingProcessor detects that a java.sql.Date will be serialized and that Jackson's in use.

Comment From: vince-0202

Jackson loads a small number of its less commonly used serializers using reflection. Without some additional metadata, these serializers cannot be loaded in a native image. You could fix your problem by providing some reflection hints that allow SqlDateSerializer to be loaded and instantiated through reflection.

@wilkinsona THX!!! My problem was solved when I added the following configuration ```@Component @ImportRuntimeHints(SqlDateSerializerRuntimeHintsConfig.SqlDateSerializerRuntimeHints.class) public class SqlDateSerializerRuntimeHintsConfig {

static class SqlDateSerializerRuntimeHints implements RuntimeHintsRegistrar {

    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        for (Constructor<?> constructor : SqlDateSerializer.class.getConstructors()) {
            hints.reflection().registerConstructor(constructor,ExecutableMode.INVOKE);
        }
    }
}

} ```

I'll transfer this issue to the Framework team as they may want to consider automatically adding the necessary reflection hints when org.springframework.aot.hint.annotation.RegisterReflectionForBindingProcessor detects that a java.sql.Date will be serialized and that Jackson's in use.

That's great!!!

Comment From: sdeleuze

It could be interesting to explore if OptionalHandlerFactory can be used in BindingReflectionHintsRegistrar#registerJacksonHints (with proper classpath check) to support the various serializers/deserializers listed there.

Comment From: sdeleuze

After a deeper look, since those hints are not very dynamic and can take advantage of conditionalOnType, I think it will be better to support those on https://github.com/oracle/graalvm-reachability-metadata side. I will create a related PR that I will link in a comment.

Comment From: sdeleuze

PR created at https://github.com/oracle/graalvm-reachability-metadata/pull/362.