quarkus: Using Jackson support for Kotlin fails in native mode.

Describe the bug When using the Jackson Kotlin module with a Kotlin data class, it fails in native mode with the following:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.capitalone.polyglot.quarkus.model.Address` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

The example code is pretty straight forward:

@ApplicationScoped
class CustomObjectMapperConfig {
    @Singleton
    @Produces
    fun objectMapper(): ObjectMapper {
        return ObjectMapper().registerModule(KotlinModule())
    }
}

data class Address(val addressLine1 : String, val addressLine2 : String, val addressLine3 : String, val addressLine4 : String, val city : String, val state : String, val country : String)

@Path("/deposits/tooling/validate-address")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class AddressValidationResource {

    @POST
    fun validateAddress(address : Address) = address.copy(
                addressLine1 = "${address.addressLine1} ${address.addressLine2} ${address.addressLine3} ${address.addressLine4}",
                addressLine2 = "",
                addressLine3 = "",
                addressLine4 = "")
}

Expected behavior (Describe the expected behavior clearly and concisely.) If I run this in JVM mode, aka mvn compile quarkus:dev, or as a runnable jar it works just fine.

Actual behavior (Describe the actual behavior clearly and concisely.)

To Reproduce Steps to reproduce the behavior: 1. 2. 3.

Configuration

# Add your application.properties here, if applicable.

Screenshots (If applicable, add screenshots to help explain your problem.)

Environment (please complete the following information):

  • Output of uname -a or ver:
  • Output of java -version:
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-20190523183630.graal2.jdk8u-src-tar-gz-b03)
OpenJDK 64-Bit GraalVM CE 19.1.0 (build 25.212-b03-jvmci-20-b04, mixed mode)
  • GraalVM version (if different from Java):
  • Quarkus version or git rev: <quarkus.version>0.21.2</quarkus.version>

Additional context (Add any other context about the problem here.) Related #3652

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 4
  • Comments: 68 (47 by maintainers)

Commits related to this issue

Most upvoted comments

I have tried a number of different configurations (slow testing cycle due to the 15minute native compile!). Upgrading to Quarkus 1.3.2, and GraalVM 20.x, did not initially make any difference. I then changed the POM to reflect your POM and it worked. I then individually started removing the POM configurations back to my original and the key (single) element that has made this work is in the Kotlin maven plugin configuration.

<javaParameters>true</javaParameters>

https://github.com/geoand/kotlin-jackson-demo/blob/cabe4f484a20c1b64332a4565060b49a3f1699c0/pom.xml#L120

So, as long as the latest POM creation through the maven archetypes generate the above setting (which appears to be the case), then this bug can be closed.

For anyone who is not creating a new project, they simply need to add the parameter to the “configuration” of the kotlin-maven-plugin, as linked to above.

Nobody is going to yell at anyone, we are as super friendly community 😃

The problem is that the behavior is different between JVM and native mode. With the no-arg plugin, in JVM the defaults are used which unfortunately is not the case in native mode.

@tellisnz-shift I also tried registering Kotlin.Any without success. I am knowledgeable of the Kotlin internals so I don’t have a path forward for the time being. I’ll try and contact the Kotlin folks and see if I get any info

Thanks for confirming and looking into it. It certainly looks like it’s an issue with how Kotlin and Jackson interact.

So I will go ahead and close this since it doesn’t appear to be a Quarkus issue. If you feel that is not correct, please reopen and provide an update.

Thanks

For a short-term work-around the following working:

@RegisterForReflection
data class Address(var addressLine1 : String = "", 
                   var addressLine2 : String = "", 
                   var addressLine3 : String = "", 
                   var addressLine4 : String = "", 
                   var city : String = "", 
                   var state : String = "", 
                   var country : String = "")

Thanks a lot for the thorough check @codemwnci!

Indeed the project generation now includes <javaParameters>true</javaParameters> : https://github.com/quarkusio/quarkus/blob/1.4.0.CR1/devtools/platform-descriptor-json/src/main/resources/templates/basic-rest/kotlin/pom.xml-template.ftl#L109 (seems like I added it in #6310)

I’ll close this issue as per both our findings

@geoand yes, it’s the payload of a POST request.

Any news?

I was able to get around this problem by explicitly adding Jackson annotations. So, this code works in JVM mode but doesn’t in native mode

@RegisterForReflection
data class CreateTaskRequest(val name: String, val description: String?)

but this code works in both JVM and native modes and keeps the class immutable.

@RegisterForReflection
data class CreateTaskRequest @JsonCreator constructor(
        @JsonProperty("name")
        val name: String,

        @JsonProperty("description")
        val description: String?
)

However, it pollutes my class and I expect to write code without any Jackson annotations as in the first example.

Kotlin: 1.3.71

So based on a suggestion from @gsmet when registering kotlin.Metadata from reflection, the process gets further but still fails. The latest error is:

Caused by: java.lang.AssertionError: Built-in class kotlin.Any is not found
        at kotlin.reflect.jvm.internal.impl.builtins.KotlinBuiltIns$3.invoke(KotlinBuiltIns.java:113)
        at kotlin.reflect.jvm.internal.impl.builtins.KotlinBuiltIns$3.invoke(KotlinBuiltIns.java:108)
        at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$MapBasedMemoizedFunction.invoke(LockBasedStorageManager.java:446)
        at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$MapBasedMemoizedFunctionToNotNull.invoke(LockBasedStorageManager.java:521)
        at kotlin.reflect.jvm.internal.impl.builtins.KotlinBuiltIns.getBuiltInClassByName(KotlinBuiltIns.java:362)
        at kotlin.reflect.jvm.internal.impl.builtins.KotlinBuiltIns.getAny(KotlinBuiltIns.java:367)
        at kotlin.reflect.jvm.internal.impl.builtins.KotlinBuiltIns.getAnyType(KotlinBuiltIns.java:642)
        at kotlin.reflect.jvm.internal.impl.descriptors.NotFoundClasses$MockClassDescriptor.<init>(NotFoundClasses.kt:60)
        at kotlin.reflect.jvm.internal.impl.descriptors.NotFoundClasses$classes$1.invoke(NotFoundClasses.kt:44)
        at kotlin.reflect.jvm.internal.impl.descriptors.NotFoundClasses$classes$1.invoke(NotFoundClasses.kt:22)
        at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$MapBasedMemoizedFunction.invoke(LockBasedStorageManager.java:446)
        at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$MapBasedMemoizedFunctionToNotNull.invoke(LockBasedStorageManager.java:521)
        at kotlin.reflect.jvm.internal.impl.descriptors.NotFoundClasses.getClass(NotFoundClasses.kt:92)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer$typeConstructor$1.invoke(TypeDeserializer.kt:109)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer.typeConstructor(TypeDeserializer.kt:113)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer.simpleType(TypeDeserializer.kt:75)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.TypeDeserializer.type(TypeDeserializer.kt:63)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.MemberDeserializer.valueParameters(MemberDeserializer.kt:417)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.MemberDeserializer.loadConstructor(MemberDeserializer.kt:342)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.computePrimaryConstructor(DeserializedClassDescriptor.kt:122)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.access$computePrimaryConstructor(DeserializedClassDescriptor.kt:34)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$primaryConstructor$1.invoke(DeserializedClassDescriptor.kt:65)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$primaryConstructor$1.invoke(DeserializedClassDescriptor.kt:34)
        at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:352)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.getUnsubstitutedPrimaryConstructor(DeserializedClassDescriptor.kt:126)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.computeConstructors(DeserializedClassDescriptor.kt:129)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.access$computeConstructors(DeserializedClassDescriptor.kt:34)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$constructors$1.invoke(DeserializedClassDescriptor.kt:66)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$constructors$1.invoke(DeserializedClassDescriptor.kt:34)
        at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:352)
        at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:408)
        at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor.getConstructors(DeserializedClassDescriptor.kt:137)
        at kotlin.reflect.jvm.internal.KClassImpl.getConstructorDescriptors(KClassImpl.kt:200)
        at kotlin.reflect.jvm.internal.KClassImpl$Data$constructors$2.invoke(KClassImpl.kt:91)
        at kotlin.reflect.jvm.internal.KClassImpl$Data$constructors$2.invoke(KClassImpl.kt:44)
        at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:92)
        at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
        at kotlin.reflect.jvm.internal.KClassImpl$Data.getConstructors(KClassImpl.kt)
        at kotlin.reflect.jvm.internal.KClassImpl.getConstructors(KClassImpl.kt:235)
        at kotlin.reflect.jvm.ReflectJvmMapping.getKotlinFunction(ReflectJvmMapping.kt:144)
        at com.fasterxml.jackson.module.kotlin.ReflectionCache.kotlinFromJava(ReflectionCache.kt:44)
        at com.fasterxml.jackson.module.kotlin.KotlinNamesAnnotationIntrospector$hasCreatorAnnotation$1.invoke(KotlinNamesAnnotationIntrospector.kt:54)
        at com.fasterxml.jackson.module.kotlin.KotlinNamesAnnotationIntrospector$hasCreatorAnnotation$1.invoke(KotlinNamesAnnotationIntrospector.kt:20)
        at com.fasterxml.jackson.module.kotlin.ReflectionCache.checkConstructorIsCreatorAnnotated(ReflectionCache.kt:46)
        at com.fasterxml.jackson.module.kotlin.KotlinNamesAnnotationIntrospector.hasCreatorAnnotation(KotlinNamesAnnotationIntrospector.kt:52)
        at com.fasterxml.jackson.databind.AnnotationIntrospector.findCreatorAnnotation(AnnotationIntrospector.java:1261)

@odilonjk the problem you are seeing is different. In your case it is not a bug but because you are returning Response, it is necessary to use @RegisterForReflection, see: https://quarkus.io/guides/rest-json#using-response

I was able to reproduce the problem. It doesn’t look like a regular Quarkus problem (since the class is registered for reflection), but more some kind of weird interop issue. I am guessing it will take a lot more digging than I can do at the moment, so let’s leave it open and I’ll take a look when I have som extra time.