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"
}
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 ajava.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.