Skip to content

Replace Moshi with Micronaut Serde (Serde 3 / JDK 25 / Gradle 9)#19

Open
MariusVolkhart wants to merge 1 commit into
mainfrom
serde-payload-converter
Open

Replace Moshi with Micronaut Serde (Serde 3 / JDK 25 / Gradle 9)#19
MariusVolkhart wants to merge 1 commit into
mainfrom
serde-payload-converter

Conversation

@MariusVolkhart

Copy link
Copy Markdown
Member

What & why

Replaces the Moshi-based JSON converter patch with Micronaut Serde, eliminating jackson-databind and moshi from the runtime classpath. Drivers: kill jackson-databind, match the production stack (Micronaut 5 / Serde 3), and Serde-generated codecs beat Moshi on deserialize.

Because Micronaut Serde 3.x requires JVM 17+, this also bumps the published fork to JDK 25 / Gradle 9.

Patches

  • 0001 Replace Jackson with Micronaut Serdejson/plain default is now MicronautSerdePayloadConverter over the application's injected io.micronaut.serde.ObjectMapper. SDK-internal protocol JSON (operation tokens, local-activity markers, failure encoding) uses a library-private mapper. Also carries the JDK-25/Gradle-9 build infra (wrapper 9.6.1, --release 25, Mockito 5.23.0, logback 1.5.18, Error Prone disabled on the JVM-25 modules).
  • 0002 / 0003 grpc-netty / groupId — replayed unchanged.
  • 0004 Add Wire protobuf support — Wire Message types serialize as native protobuf binary (protobuf/wire); the Moshi WireJsonAdapterFactory (nested-Wire-in-JSON) path is removed.

Architecture notes

  • User payloads use your injected ObjectMapper; SDK-internal serialization uses a separate library-private mapper, so the SDK-specific formats (Duration-as-millis, OperationTokenType-as-int) never touch your data.
  • Those SDK-specific serdes are typed Serde<Object> and bound via field using=, so they are not globally indexed and do not leak into a consuming app's DI ObjectMapper (verified by SerdeIsolationTest against a real ApplicationContext).

Consumer behavior changes (vs the old Jackson/Moshi converter)

Micronaut Serde is reflection-free / compile-time, so:

  • Workflow arg/result types must be @Serdeable (your Micronaut 5 models already are).
  • Public-field POJOs serialize to {} — use records, getters, or @Introspected(accessKind = {FIELD, METHOD}).
  • java.time.Duration serializes as integer nanoseconds (Serde default), not an ISO-8601 string.
  • An empty byte[] field inside a large bean may deserialize as null.
  • Wire Message types are protobuf-binary, not JSON.

Verification

  • All 4 patches apply cleanly from a fresh upstream clone.
  • Full compile green at JDK 25 / Gradle 9.
  • Dependency gate clean: no jackson-databind, no moshi (okio remains only as a wire-runtime transitive).
  • Best clean full run: 1510 tests, 1 failure.

One deterministic test failure remains — SearchAttributesTest.testInvalidSearchAttributeType. It is not a Serde-converter regression: the search-attribute encode path, SearchAttributePayloadConverter, and the test-server validation are byte-identical to upstream (the only SA-path file this PR touches is temporal-test-server/build.gradle, a build-only change), and the test starts a no-arg workflow so the data converter is barely involved. It is the in-process test server's invalid-type-rejection behavior under JDK 25; production search-attribute validation is unaffected (a real Temporal server validates server-side, and the SDK sends the same encoding as upstream). It likely reproduces on upstream-at-JDK-25 and should be triaged as a JDK-25 test-infra item. Please confirm the suite in CI (now JDK 25) on adequate resources — the parallel test suite flakes badly under machine load.

Note

.github/workflows/manual-build.yml also includes a pre-existing uncommitted version_override workflow input (unrelated working-tree edit) that got bundled with the JDK-25 change.

🤖 Generated with Claude Code

@MariusVolkhart MariusVolkhart force-pushed the serde-payload-converter branch from 02aa93f to 5b3d995 Compare June 27, 2026 23:43
Rewrites patch 0001 (Jackson -> Micronaut Serde 3.x): user payloads use
the application's injected io.micronaut.serde.ObjectMapper; SDK-internal
protocol JSON (operation tokens, local-activity markers, failure
encoding) uses a library-private mapper whose SDK-specific formats
(Duration-as-millis, OperationTokenType-as-int) are field-scoped and
proven not to leak into a consuming app's DI mapper. Removes
jackson-databind and moshi from the runtime classpath (okio remains as a
wire-runtime transitive).

Two Serde behavior differences vs the old Jackson/Moshi converter were
handled in 0001: public-field POJOs need @introspected(FIELD) access
(Serde is getter-based); and Serde's unconditional boolean->long coercion
required SearchAttributePayloadConverter.decodeAsType to reject
type-mismatched attributes via their encoded type metadata rather than
relying on a decode exception.

The Serde 3.x requirement bumps the build to JDK 25 / Gradle 9 (wrapper
9.x, --release 25, Mockito 5.23.0, logback 1.5.18, Error Prone disabled
on the 25 modules). Trims patch 0004 (Wire) to drop the Moshi
WireJsonAdapterFactory integration; Wire Message types serialize as
native protobuf binary.

overlay/gradle.properties: drop moshiVersion, add Serde/Micronaut
versions + build heap. README: rebuilt patch table, Serde behavior
notes. CI: JDK 25.

Note: .github/workflows/manual-build.yml also carries a pre-existing
uncommitted version_override input (unrelated working-tree edit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MariusVolkhart MariusVolkhart force-pushed the serde-payload-converter branch from 5b3d995 to 856b229 Compare June 28, 2026 00:25
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.

1 participant