Overview
We update our spring boot version to 3.0.0 and build native image with below mentioned grallvm version.
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)
While call the API which use Jackson-databind annotation then it will return with an error.
Related Issues
-
29426
-
29386
Comment From: mhalbritter
If you'd like us to spend some time investigating, please take the time to provide a complete minimal sample (something that we can unzip or git clone, build, and deploy) that reproduces the problem.
Comment From: dilipdhankecha2530
@mhalbritter Yeah, sure I will do it. and share it over here.
Comment From: dilipdhankecha2530
@mhalbritter
Please, you can take a look into test.zip.
I got an error as I mentioned below.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Builder class `com.demo.wrapper.model.Test$Builder` does not have build method (name: 'build')
at [Source: (String)"{"id":"default"}"; line: 1, column: 1]
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1909) ~[wrapper:2.14.1]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder.buildBuilderBased(BeanDeserializerBuilder.java:440) ~[na:na]
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBuilderBasedDeserializer(BeanDeserializerFactory.java:362) ~[na:na]
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBuilderBasedDeserializer(BeanDeserializerFactory.java:170) ~[na:na]
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:343) ~[na:na]
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264) ~[na:na]
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244) ~[na:na]
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142) ~[na:na]
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:648) ~[wrapper:2.14.1]
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4861) ~[wrapper:2.14.1]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4731) ~[wrapper:2.14.1]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3677) ~[wrapper:2.14.1]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3645) ~[wrapper:2.14.1]
at com.demo.wrapper.web.TestResource.test(TestResource.java:23) ~[wrapper:na]
at java.base@17.0.5/java.lang.reflect.Method.invoke(Method.java:568) ~[wrapper:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[wrapper:6.0.2]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[wrapper:6.0.2]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[wrapper:6.0.2]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[wrapper:6.0.2]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[wrapper:6.0.2]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[wrapper:6.0.2]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080) ~[wrapper:6.0.2]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973) ~[wrapper:6.0.2]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003) ~[wrapper:6.0.2]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:906) ~[wrapper:6.0.2]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:731) ~[wrapper:6.0]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[wrapper:6.0.2]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[wrapper:6.0]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[wrapper:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[wrapper:6.0.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[wrapper:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[wrapper:6.0.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[wrapper:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:109) ~[wrapper:6.0.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[wrapper:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[wrapper:6.0.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[wrapper:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[na:na]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[na:na]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[na:na]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[na:na]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[wrapper:10.1.1]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[na:na]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[wrapper:10.1.1]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[na:na]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[na:na]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[na:na]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[wrapper:10.1.1]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[na:na]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739) ~[na:na]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[wrapper:10.1.1]
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) ~[wrapper:na]
at org.graalvm.nativeimage.builder/com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:775) ~[wrapper:na]
at org.graalvm.nativeimage.builder/com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:203) ~[na:na]
You can use below curl to reproduce the issue.
curl --location --request POST 'localhost:8080/api/test' \
--header 'Content-Type: text/plain' \
--data-raw '{"id":"default"}'
Comment From: mhalbritter
Thanks for the sample. It looks like that the Jackson builder feature is not supported yet.
@JsonDeserialize(builder = Test.Builder.class)
You can either remove the builder or register the ctor and methods from your builder to be reflection-enabled:
class TestresourceRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerType(Test.Builder.class, MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
}
}
and then use
@ImportRuntimeHints(TestresourceRuntimeHints.class)
on any bean.
Comment From: sbrannen
If we address this issue, I think we should reopen #29386 and address it as well.
Comment From: bclozel
As part of this issue, we should consider whether we want to introduce specific AOT rules for Jackson. Since there are many features and variants in Jackson, I'm not sure we should invest in that area.
Comment From: bclozel
We could expand the scope of our @RegisterReflectionForBinding
and use it to process method parameters and return types of controller methods.
This means supporting various features like:
@JsonNaming
strategies@JsonDeserialize
,@JsonSerialize
@JsonGetter
,@JsonSetter
@JsonValue
@JsonCreator
- Detecting
@JsonSubTypes
types if only the interface type is detected through AOT processing
We could also refine the binding algorithm to include/ignore @JsonIgnoreProperties
, @JsonIgnore
and @JsonIgnoreType
, @JsonAutoDetect
annotated elements.
All of that should also support custom annotations with @JacksonAnnotationsInside
.
Note that all of those features can be turned off by a Jackson configuration flag through the MapperFeature
, which is not accessible during the AOT phase.
With that in mind, I'm not in favor of implementing Jackson-specific behavior, but keep using our general "reflection for binding" that matches a lot of cases already. I think this should be handled in a GraalVM Feature
or better, through a build-time mechanism provided by the Jackson project itself: keeping up with all the changes in Jackson is an important maintenance burden in this space.
Comment From: mhalbritter
keeping up with all the changes in Jackson is an important maintenance burden in this space.
I agree on not implementing Jackson-specific behavior.
Comment From: sdeleuze
On one side I understand our reluctance related to the maintenance burden especially given the various annotations we would have to support, on another side:
- We need to be consistent. Why would we support JPA @Converter
and @Convert
annotation like we do in PersistenceManagedTypesBeanRegistrationAotProcessor
and not Jackson similar use case?
- Spring is already in charge of providing hints for classes serialized by Jackson via the knowledge it has of related classes via for example Spring MVC/WebFlux programming model or RegisterReflectionForBinding
, conceptually this is similar.
- The end user will have to go threw the pain of multiple native build failure which is not ideal
- Jackson is probably the most popular dependency we support
Since BindingReflectionHintsRegistrar
already has some logic related to the handing of @JacksonAnnotation
, my proposal would be simply to refine that logic to add invoke hints on declared constructors for class attribute of annotations annotated with @JacksonAnnotation
. Looks like a small change, rather maintainable which translate as a big improvement for native users. Any thoughts?
Comment From: sbrannen
Since
BindingReflectionHintsRegistrar
already has some logic related to the handing of@JacksonAnnotation
, my proposal would be simply to refine that logic to add invoke hints on declared constructors for class attribute of annotations annotated with@JacksonAnnotation
. Looks like a small change, rather maintainable which translate as a big improvement for native users. Any thoughts?
I agree that not doing anything additional for Jackson use cases will put a large burden on our users. If the Jackson team some day introduces a GraalVM Feature
(or similar), that would of course be great, but who knows if that day will ever come.
So, if we can make a big improvement with such a small change like you've proposed Sebastien, I am in favor of that.
Comment From: sdeleuze
After discussing with @bclozel and @jhoeller, let's give it a try. If the implementation is more involved than expected, we may reconsider implementing this.