diff --git a/temporal-kotlin/src/main/kotlin/io/temporal/common/converter/KotlinObjectMapperFactory.kt b/temporal-kotlin/src/main/kotlin/io/temporal/common/converter/KotlinObjectMapperFactory.kt index 621d02fa4d..b4b5f925e1 100644 --- a/temporal-kotlin/src/main/kotlin/io/temporal/common/converter/KotlinObjectMapperFactory.kt +++ b/temporal-kotlin/src/main/kotlin/io/temporal/common/converter/KotlinObjectMapperFactory.kt @@ -2,19 +2,19 @@ package io.temporal.common.converter import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.registerKotlinModule class KotlinObjectMapperFactory { companion object { @JvmStatic fun new(): ObjectMapper { - val mapper = JacksonJsonPayloadConverter.newDefaultObjectMapper() - - // use deprecated constructor instead of builder to maintain compatibility with old jackson versions - @Suppress("deprecation") - val km = KotlinModule() - mapper.registerModule(km) - return mapper + // Let jackson-module-kotlin construct the module rather than calling a constructor here. + // `KotlinModule()` compiles to the synthetic all-defaults overload of its deprecated + // constructor, and that parameter list changed in 2.11, 2.12 and 2.16, so the call only + // linked against the versions sharing the shape we happened to build against and threw + // NoSuchMethodError on every other version. `registerKotlinModule` has kept a single + // signature since 2.9.0, which is the whole Jackson range this SDK supports. + return JacksonJsonPayloadConverter.newDefaultObjectMapper().registerKotlinModule() } } } diff --git a/temporal-kotlin/src/test/kotlin/io/temporal/common/converter/KotlinObjectMapperFactoryTest.kt b/temporal-kotlin/src/test/kotlin/io/temporal/common/converter/KotlinObjectMapperFactoryTest.kt new file mode 100644 index 0000000000..2d26355272 --- /dev/null +++ b/temporal-kotlin/src/test/kotlin/io/temporal/common/converter/KotlinObjectMapperFactoryTest.kt @@ -0,0 +1,25 @@ +package io.temporal.common.converter + +import org.junit.Assert.assertEquals +import org.junit.Test + +class KotlinObjectMapperFactoryTest { + + data class TestPayload(val name: String, val count: Int) + + /** + * A data class has no no-arg constructor, so Jackson can only deserialize it when the Kotlin + * module is registered. This also guards against [KotlinObjectMapperFactory.new] failing to link + * against the jackson-module-kotlin version present at runtime, which is not necessarily the one + * the SDK was compiled against. + */ + @Test + fun `new should return a mapper that round-trips a Kotlin data class`() { + val mapper = KotlinObjectMapperFactory.new() + + val value = TestPayload("payload", 42) + val roundTripped = mapper.readValue(mapper.writeValueAsString(value), TestPayload::class.java) + + assertEquals(value, roundTripped) + } +}