Skip to content

Fix KotlinObjectMapperFactory on newer Jackson - #3095

Closed
kevinrobayna wants to merge 1 commit into
temporalio:mainfrom
kevinrobayna:krobayna_kotlin_object_mapper_factory_jackson_compat
Closed

kevinrobayna wants to merge 1 commit into
temporalio:mainfrom
kevinrobayna:krobayna_kotlin_object_mapper_factory_jackson_compat

Conversation

@kevinrobayna

Copy link
Copy Markdown

What changed?

KotlinObjectMapperFactory.new() now registers the Kotlin module through the
registerKotlinModule() extension instead of constructing KotlinModule itself, plus a
temporal-kotlin test that round-trips a Kotlin data class through the returned mapper (a data
class cannot be deserialized unless the module is registered).

Why?

KotlinModule() with all-default arguments compiles to a call to the synthetic all-defaults
overload of KotlinModule's deprecated constructor, so the emitted descriptor pins whatever
parameter list that constructor happened to have in the Jackson version the SDK was built against:

KotlinModule."<init>":(IZZZLcom/fasterxml/jackson/module/kotlin/SingletonSupport;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V

That parameter list has changed repeatedly — it gained arguments in 2.11, 2.12 and 2.16, and
SingletonSupport later became a boolean — so the call links only against the versions sharing
the shape it was compiled against. Built against 2.15.4 (the current jacksonVersion) and run
against 2.21.4, every caller of new() gets:

java.lang.NoSuchMethodError: 'void com.fasterxml.jackson.module.kotlin.KotlinModule.<init>(int, boolean, boolean, boolean, com.fasterxml.jackson.module.kotlin.SingletonSupport, boolean, int, kotlin.jvm.internal.DefaultConstructorMarker)'

The comment being removed says the deprecated constructor was used "to maintain compatibility with
old jackson versions", but it has the opposite effect: the same call also fails on 2.9.0 through
2.11 — the low end of the [2.9.0,) range declared next to jacksonVersion in build.gradle.

registerKotlinModule() leaves the constructor choice to jackson-module-kotlin itself, and its own
signature has been unchanged since 2.9.0, so the emitted call site stays valid across the whole
supported range.

Validation

./gradlew :temporal-kotlin:test passes in full (40 tests, including the new one), and
./gradlew :temporal-kotlin:spotlessCheck passes.

Since the module's own suite only ever runs against one Jackson version, I also checked the
cross-version behaviour two ways, both compiling against 2.15.4 to mirror how the SDK is released:

  1. The real KotlinObjectMapperFactory.kt and the new test, compiled with kotlinc 1.9.24 against
    the published temporal-sdk jar, then run under JUnit against Jackson 2.15.4 and 2.21.4. The
    new test passes on both with this change; against current main it passes on 2.15.4 and fails
    on 2.21.4 with the NoSuchMethodError above.

  2. A reduced two-line reproduction of the old and new module-registration paths, run against every
    Jackson minor in the supported range:

    Jackson before after
    2.9.0, 2.10.0, 2.11.0 NoSuchMethodError pass
    2.12.0, 2.13.0, 2.14.0, 2.15.4 pass pass
    2.16.0, 2.17.0, 2.18.0, 2.19.0, 2.21.4 NoSuchMethodError pass

One thing worth flagging: the new test passes both before and after on 2.15.4, which is the only
Jackson version any CI job uses. jacksonVersion is not among the edgeDepsTest overrides
(unlike micrometerVersion, slf4jVersion and logbackVersion), so nothing in CI ever resolves
this constructor against a newer Jackson — which is why this went unnoticed. Adding Jackson to that
matrix would catch the whole class of bug; I've left it out to keep this PR to a single logical
change, but I'm happy to follow up with it if you'd like.

Breaking changes?

None. The public API is unchanged — javap still reports
public static final com.fasterxml.jackson.databind.ObjectMapper new() — and only the
module-construction path inside the method changes. The resulting mapper is configured identically:
registerKotlinModule() builds a KotlinModule with the same defaults the previous all-defaults
constructor call requested.

Server PR

Not required.

KotlinObjectMapperFactory.new() constructed KotlinModule with all
default arguments. Kotlin compiles that into a call to the synthetic
all-defaults overload of KotlinModule's deprecated constructor, so the
emitted descriptor pins whatever parameter list that constructor had in
the Jackson version the SDK was built against.

That parameter list has changed repeatedly - it gained arguments in
2.11, 2.12 and 2.16, and SingletonSupport later became a boolean - so a
call compiled against 2.15.4 links only against 2.12 through 2.15 and
throws NoSuchMethodError on anything else. Built against 2.15.4 and run
against 2.21.4:

    java.lang.NoSuchMethodError: 'void com.fasterxml.jackson.module.
    kotlin.KotlinModule.<init>(int, boolean, boolean, boolean, com.
    fasterxml.jackson.module.kotlin.SingletonSupport, boolean, int,
    kotlin.jvm.internal.DefaultConstructorMarker)'

The deprecated constructor was chosen to keep compatibility with old
Jackson versions, but it never achieved that: the same call also fails
on 2.9.0 through 2.11, the low end of the supported [2.9.0,) range.

registerKotlinModule() leaves the constructor choice to
jackson-module-kotlin, and its own signature has been unchanged since
2.9.0. Compiling against 2.15.4 and running against 2.9.0, 2.10, 2.11,
2.12, 2.13, 2.14, 2.15.4, 2.16, 2.17, 2.18, 2.19 and 2.21.4, the
current code passes only on 2.12 through 2.15 while this change passes
on every one of them.

No public API change: new() is still a static method returning
ObjectMapper.
@kevinrobayna
kevinrobayna marked this pull request as ready for review September 22, 2026 14:21
@kevinrobayna
kevinrobayna requested a review from a team as a code owner September 22, 2026 14:21
@Quinn-With-Two-Ns

Copy link
Copy Markdown
Contributor

Hi, there is already an approved open PR for this #2072 that cover more then this. Please make sure to check open PRs before starting work on an issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants