feat(kotlin-client): add Jackson 3 support with useJackson3 option#23161
feat(kotlin-client): add Jackson 3 support with useJackson3 option#23161yonatankarp wants to merge 8 commits intoOpenAPITools:masterfrom
Conversation
2bdb660 to
4535b51
Compare
b29d00d to
b61a616
Compare
Wire the existing AbstractKotlinCodegen Jackson 3 infrastructure into
the kotlin client generator. When useJackson3=true (requires
serializationLibrary=jackson), all templates use the tools.jackson
package instead of com.fasterxml.jackson, and build.gradle pulls
jackson-module-kotlin 3.0.1 without the separate JSR-310 module.
- Register useJackson3 CLI option in KotlinClientCodegen
- Add validation: requires jackson serialization, incompatible with openApiNullable
- Replace hardcoded com.fasterxml.jackson with {{jacksonPackage}} in all
model, serializer, and library-specific templates
- Make JavaTimeModule conditional (not needed in Jackson 3) for jvm-ktor
- Add Jackson 3 dependency block in build.gradle.mustache
- Add tests for validation and generated output
- Add kotlin-jackson3 sample config and generated sample
- Add sample to CI workflow matrix
- Regenerate generator docs
Jackson 3 only moves databind/core/module to tools.jackson package. The annotations artifact stays at com.fasterxml.jackson.annotation. Also add AbstractKotlinCodegen Jackson 3 infrastructure so this branch is self-contained for CI testing.
Jackson 3 moved/renamed several APIs: - findAndRegisterModules() not needed (modules auto-discovered) - SerializationFeature.WRITE_DATES_AS_TIMESTAMPS -> DateTimeFeature - DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE -> EnumFeature Add conditionals in Serializer.kt.mustache for Jackson 2 vs 3.
Jackson 3 ObjectMapper is immutable — no more chaining .configure()
and .setSerializationInclusion(). Use jsonMapper {} builder DSL with:
- changeDefaultPropertyInclusion for NON_ABSENT inclusion
- enable/disable for DateTimeFeature, EnumFeature, DeserializationFeature
- addModule(kotlinModule()) instead of jacksonObjectMapper()
The upstream kotlin-spring PR added a guard throwing IllegalArgumentException for useJackson3 on kotlin-client. Since this branch implements that support, remove the guard while keeping the proper validation below it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Picks up constant renaming from upstream (OpenAPITools#23188).
56fde75 to
69d1430
Compare
Add useSpringBoot4 option that auto-enables Jackson 3 and generates RestClient code using JacksonJsonHttpMessageConverter with Spring Boot 4.
There was a problem hiding this comment.
7 issues found across 104 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="samples/client/petstore/kotlin-jackson3/docs/PetApi.md">
<violation number="1" location="samples/client/petstore/kotlin-jackson3/docs/PetApi.md:328">
P1: Malformed Markdown parameter table: The table header row (`| Name | Type | Description | Notes |`) appears mid-table between parameters instead of only at the beginning. This indicates a bug in the `api_doc.mustache` template where headers are incorrectly rendered inside parameter loops rather than once for all parameters, which will break HTML rendering in published documentation.</violation>
</file>
<file name="samples/client/petstore/kotlin-jackson3/src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt">
<violation number="1" location="samples/client/petstore/kotlin-jackson3/src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt:14">
P1: Extension property `isRedirect` is shadowed by OkHttp's member property and will never be invoked. The `@Suppress("EXTENSION_SHADOWED_BY_MEMBER")` annotation acknowledges this conflict. When `response.isRedirect` is called in `ApiClient.kt:427`, Kotlin resolves to OkHttp's built-in implementation which only returns true for specific codes (300/301/302/303/307/308), not the documented 300-399 range. HTTP codes like 304, 305 will incorrectly fall through to `ServerError` instead of `Redirection`. Consider renaming to `isAnyRedirect` or `is3xx` to avoid shadowing.</violation>
</file>
<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java">
<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java:474">
P1: BUG: Setting `useSpringBoot4=false` incorrectly enables Jackson 3 and may crash the generator. The condition only checks if the key exists, not its boolean value.</violation>
</file>
<file name="samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3/src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt">
<violation number="1" location="samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3/src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt:5">
P2: Comment contradicts implementation: The documentation states body is excluded for caching purposes, but body is included as a data class constructor property, which breaks the stated caching/dedup invariant. Kotlin data class properties participate in equals() and hashCode(), so two requests with different payloads will not share config identity as the comment suggests.</violation>
</file>
<file name="samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3/src/main/kotlin/org/openapitools/client/apis/PetApi.kt">
<violation number="1" location="samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3/src/main/kotlin/org/openapitools/client/apis/PetApi.kt:60">
P1: The 'Content-Type' header is overwritten. The map is first assigned "application/json", then immediately overwritten with "application/xml". Since the RestClient is configured with JacksonJsonHttpMessageConverter (which serializes the Pet object as JSON), the request will send JSON data with an incorrect XML content-type header, causing server-side parsing failures.</violation>
</file>
<file name="modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/infrastructure/ApiClient.kt.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/infrastructure/ApiClient.kt.mustache:53">
P1: Incompatible Jackson version with Retrofit2: `{{jacksonPackage}}.databind.ObjectMapper` (Jackson 3 when useJackson3=true) is passed to `JacksonConverterFactory.create()` which expects `com.fasterxml.jackson.databind.ObjectMapper` (Jackson 2). This type mismatch will cause compilation errors for generated jvm-retrofit2 clients when useJackson3=true.</violation>
</file>
<file name=".github/workflows/samples-kotlin-client.yaml">
<violation number="1" location=".github/workflows/samples-kotlin-client.yaml:29">
P1: Missing CI coverage for new sample `kotlin-jvm-spring-4-restclient-jackson3`. The PR adds `kotlin-jackson3` to the workflow but omits the Spring Boot 4 Jackson 3 sample, creating a CI blind spot where this sample can break without detection.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| ### Parameters | ||
| | **petId** | **kotlin.Long**| ID of pet that needs to be updated | | | ||
| | **name** | **kotlin.String**| Updated name of the pet | [optional] | |
There was a problem hiding this comment.
P1: Malformed Markdown parameter table: The table header row (| Name | Type | Description | Notes |) appears mid-table between parameters instead of only at the beginning. This indicates a bug in the api_doc.mustache template where headers are incorrectly rendered inside parameter loops rather than once for all parameters, which will break HTML rendering in published documentation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-jackson3/docs/PetApi.md, line 328:
<comment>Malformed Markdown parameter table: The table header row (`| Name | Type | Description | Notes |`) appears mid-table between parameters instead of only at the beginning. This indicates a bug in the `api_doc.mustache` template where headers are incorrectly rendered inside parameter loops rather than once for all parameters, which will break HTML rendering in published documentation.</comment>
<file context>
@@ -0,0 +1,397 @@
+
+### Parameters
+| **petId** | **kotlin.Long**| ID of pet that needs to be updated | |
+| **name** | **kotlin.String**| Updated name of the pet | [optional] |
+| Name | Type | Description | Notes |
+| ------------- | ------------- | ------------- | ------------- |
</file context>
| * Provides an extension to evaluation whether the response is a 3xx code | ||
| */ | ||
| @Suppress("EXTENSION_SHADOWED_BY_MEMBER") | ||
| val Response.isRedirect : Boolean get() = this.code in 300..399 |
There was a problem hiding this comment.
P1: Extension property isRedirect is shadowed by OkHttp's member property and will never be invoked. The @Suppress("EXTENSION_SHADOWED_BY_MEMBER") annotation acknowledges this conflict. When response.isRedirect is called in ApiClient.kt:427, Kotlin resolves to OkHttp's built-in implementation which only returns true for specific codes (300/301/302/303/307/308), not the documented 300-399 range. HTTP codes like 304, 305 will incorrectly fall through to ServerError instead of Redirection. Consider renaming to isAnyRedirect or is3xx to avoid shadowing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-jackson3/src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt, line 14:
<comment>Extension property `isRedirect` is shadowed by OkHttp's member property and will never be invoked. The `@Suppress("EXTENSION_SHADOWED_BY_MEMBER")` annotation acknowledges this conflict. When `response.isRedirect` is called in `ApiClient.kt:427`, Kotlin resolves to OkHttp's built-in implementation which only returns true for specific codes (300/301/302/303/307/308), not the documented 300-399 range. HTTP codes like 304, 305 will incorrectly fall through to `ServerError` instead of `Redirection`. Consider renaming to `isAnyRedirect` or `is3xx` to avoid shadowing.</comment>
<file context>
@@ -0,0 +1,24 @@
+ * Provides an extension to evaluation whether the response is a 3xx code
+ */
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+val Response.isRedirect : Boolean get() = this.code in 300..399
+
+/**
</file context>
| if (additionalProperties.containsKey(USE_SPRING_BOOT4)) { | ||
| convertPropertyToBooleanAndWriteBack(USE_SPRING_BOOT4); | ||
| additionalProperties.put(USE_JACKSON_3, "true"); | ||
| setUseJackson3(true); |
There was a problem hiding this comment.
P1: BUG: Setting useSpringBoot4=false incorrectly enables Jackson 3 and may crash the generator. The condition only checks if the key exists, not its boolean value.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java, line 474:
<comment>BUG: Setting `useSpringBoot4=false` incorrectly enables Jackson 3 and may crash the generator. The condition only checks if the key exists, not its boolean value.</comment>
<file context>
@@ -469,9 +471,10 @@ public void processOpts() {
- if (isUseJackson3()) {
- throw new IllegalArgumentException(
- "useJackson3 is not yet supported for kotlin-client. Jackson 3 support for kotlin-client will be added in a future release.");
+ if (additionalProperties.containsKey(USE_SPRING_BOOT4)) {
+ convertPropertyToBooleanAndWriteBack(USE_SPRING_BOOT4);
+ additionalProperties.put(USE_JACKSON_3, "true");
</file context>
| if (additionalProperties.containsKey(USE_SPRING_BOOT4)) { | |
| convertPropertyToBooleanAndWriteBack(USE_SPRING_BOOT4); | |
| additionalProperties.put(USE_JACKSON_3, "true"); | |
| setUseJackson3(true); | |
| if (additionalProperties.containsKey(USE_SPRING_BOOT4) && Boolean.parseBoolean(additionalProperties.get(USE_SPRING_BOOT4).toString())) { | |
| convertPropertyToBooleanAndWriteBack(USE_SPRING_BOOT4); | |
| additionalProperties.put(USE_JACKSON_3, "true"); | |
| setUseJackson3(true); | |
| } |
| val localVariableBody = pet | ||
| val localVariableQuery = mutableMapOf<kotlin.String, kotlin.collections.List<kotlin.String>>() | ||
| val localVariableHeaders: MutableMap<String, String> = mutableMapOf() | ||
| localVariableHeaders["Content-Type"] = "application/json" |
There was a problem hiding this comment.
P1: The 'Content-Type' header is overwritten. The map is first assigned "application/json", then immediately overwritten with "application/xml". Since the RestClient is configured with JacksonJsonHttpMessageConverter (which serializes the Pet object as JSON), the request will send JSON data with an incorrect XML content-type header, causing server-side parsing failures.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3/src/main/kotlin/org/openapitools/client/apis/PetApi.kt, line 60:
<comment>The 'Content-Type' header is overwritten. The map is first assigned "application/json", then immediately overwritten with "application/xml". Since the RestClient is configured with JacksonJsonHttpMessageConverter (which serializes the Pet object as JSON), the request will send JSON data with an incorrect XML content-type header, causing server-side parsing failures.</comment>
<file context>
@@ -0,0 +1,348 @@
+ val localVariableBody = pet
+ val localVariableQuery = mutableMapOf<kotlin.String, kotlin.collections.List<kotlin.String>>()
+ val localVariableHeaders: MutableMap<String, String> = mutableMapOf()
+ localVariableHeaders["Content-Type"] = "application/json"
+ localVariableHeaders["Content-Type"] = "application/xml"
+ localVariableHeaders["Accept"] = "application/xml, application/json"
</file context>
| {{/moshi}} | ||
| {{#jackson}} | ||
| import com.fasterxml.jackson.databind.ObjectMapper | ||
| import {{jacksonPackage}}.databind.ObjectMapper |
There was a problem hiding this comment.
P1: Incompatible Jackson version with Retrofit2: {{jacksonPackage}}.databind.ObjectMapper (Jackson 3 when useJackson3=true) is passed to JacksonConverterFactory.create() which expects com.fasterxml.jackson.databind.ObjectMapper (Jackson 2). This type mismatch will cause compilation errors for generated jvm-retrofit2 clients when useJackson3=true.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/infrastructure/ApiClient.kt.mustache, line 53:
<comment>Incompatible Jackson version with Retrofit2: `{{jacksonPackage}}.databind.ObjectMapper` (Jackson 3 when useJackson3=true) is passed to `JacksonConverterFactory.create()` which expects `com.fasterxml.jackson.databind.ObjectMapper` (Jackson 2). This type mismatch will cause compilation errors for generated jvm-retrofit2 clients when useJackson3=true.</comment>
<file context>
@@ -50,7 +50,7 @@ import com.squareup.moshi.Moshi
{{/moshi}}
{{#jackson}}
-import com.fasterxml.jackson.databind.ObjectMapper
+import {{jacksonPackage}}.databind.ObjectMapper
import retrofit2.converter.jackson.JacksonConverterFactory
{{/jackson}}
</file context>
| - samples/client/petstore/kotlin-explicit | ||
| - samples/client/petstore/kotlin-gson | ||
| - samples/client/petstore/kotlin-jackson | ||
| - samples/client/petstore/kotlin-jackson3 |
There was a problem hiding this comment.
P1: Missing CI coverage for new sample kotlin-jvm-spring-4-restclient-jackson3. The PR adds kotlin-jackson3 to the workflow but omits the Spring Boot 4 Jackson 3 sample, creating a CI blind spot where this sample can break without detection.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/samples-kotlin-client.yaml, line 29:
<comment>Missing CI coverage for new sample `kotlin-jvm-spring-4-restclient-jackson3`. The PR adds `kotlin-jackson3` to the workflow but omits the Spring Boot 4 Jackson 3 sample, creating a CI blind spot where this sample can break without detection.</comment>
<file context>
@@ -26,6 +26,7 @@ jobs:
- samples/client/petstore/kotlin-explicit
- samples/client/petstore/kotlin-gson
- samples/client/petstore/kotlin-jackson
+ - samples/client/petstore/kotlin-jackson3
- samples/client/petstore/kotlin-model-prefix-type-mappings
# needs Android configured
</file context>
|
|
||
| /** | ||
| * Defines a config object for a given request. | ||
| * NOTE: This object doesn't include 'body' because it |
There was a problem hiding this comment.
P2: Comment contradicts implementation: The documentation states body is excluded for caching purposes, but body is included as a data class constructor property, which breaks the stated caching/dedup invariant. Kotlin data class properties participate in equals() and hashCode(), so two requests with different payloads will not share config identity as the comment suggests.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3/src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt, line 5:
<comment>Comment contradicts implementation: The documentation states body is excluded for caching purposes, but body is included as a data class constructor property, which breaks the stated caching/dedup invariant. Kotlin data class properties participate in equals() and hashCode(), so two requests with different payloads will not share config identity as the comment suggests.</comment>
<file context>
@@ -0,0 +1,19 @@
+
+/**
+ * Defines a config object for a given request.
+ * NOTE: This object doesn't include 'body' because it
+ * allows for caching of the constructed object
+ * for many request definitions.
</file context>
| @@ -0,0 +1,10 @@ | |||
| generatorName: kotlin | |||
| outputDir: samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3 | |||
There was a problem hiding this comment.
please add this new folder to the workflow file as well (samples-kotlin-client.yaml)
There was a problem hiding this comment.
Yap, I moved it from draft just to trigger the review - thanks for the comment!
| {{#enumUnknownDefaultCase}} | ||
| enable(EnumFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE) | ||
| {{/enumUnknownDefaultCase}} | ||
| disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) |
There was a problem hiding this comment.
I believe the default for this serialization feature WRITE_DATES_AS_TIMESTAMPS changed in Jackson 3 from true in Jackson 2 to false in Jackson 3. Perhaps it can be omitted from the generated code if on Jackson 3?
I believe the same applies for FAIL_ON_UNKNOWN_PROPERTIES, however, as this is controlled with configuration options, there must be a check on that option.
Summary
AbstractKotlinCodegenJackson 3 infrastructure into the kotlin client generatoruseJackson3=true(requiresserializationLibrary=jackson), all templates use thetools.jacksonpackage instead ofcom.fasterxml.jackson, andbuild.gradlepullsjackson-module-kotlin 3.0.1without the separate JSR-310 moduleuseJackson3CLI option with validation (requires jackson serialization, incompatible withopenApiNullable)com.fasterxml.jacksonwith{{jacksonPackage}}in all model, serializer, and library-specific templatesJavaTimeModuleconditional (not needed in Jackson 3) for jvm-ktorkotlin-jackson3sample config, generated sample, CI workflow entry, and regenerated docsTest plan
useJackson3with non-jackson serialization and withopenApiNullableimport tools.jackson.annotation.JsonProperty(notcom.fasterxml.jackson)build.gradlecontainstools.jackson.module:jackson-module-kotlinand nojackson-datatype-jsr310samples-kotlin-clientworkflowSummary by cubic
Adds Jackson 3 support to the Kotlin client via
useJackson3and adds Spring Boot 4 support for thejvm-spring-restclientlibrary viauseSpringBoot4, switching generated code totools.jacksonpackages (annotations staycom.fasterxml.jackson.annotation) and using Spring’sJacksonJsonHttpMessageConverterwithRestClient.New Features
useJackson3CLI option with validation (requiresserializationLibrary=jackson; incompatible withopenApiNullable); removed the old guard.useSpringBoot4option forjvm-spring-restclientthat auto-enables Jackson 3, generatesRestClientcode, and usesJacksonJsonHttpMessageConverter; Gradle setsspring_boot_versionto 4.0.1.{{jacksonPackage}}across models and JVM clients (okhttp,retrofit2,ktor,vertx,jvm-spring-restclient); annotations remaincom.fasterxml.jackson.annotation.jsonMapper { addModule(kotlinModule()) ... }, sets NON_ABSENT inclusion, disables timestamps viaDateTimeFeature, and optionally enablesEnumFeature;SerializationFeatureis used only for Jackson 2 andfindAndRegisterModulesis not used.tools.jackson.module:jackson-module-kotlin:3.0.1when enabled and dropsjackson-datatype-jsr310;jvm-ktorregistersJavaTimeModuleonly for Jackson 2.kotlin-jackson3andkotlin-jvm-spring-4-restclient-jackson3samples, CI matrix entry for the former, updated docs, and tests for option validation and generated imports/dependencies.FILESmanifests for CI stability.Migration
-p serializationLibrary=jackson -p useJackson3=true(do not combine withopenApiNullable).-p library=jvm-spring-restclient -p useSpringBoot4=true(auto-enables Jackson 3). Generated code usestools.jackson.*for databind/core/module andcom.fasterxml.jackson.annotationfor annotations;jvm-ktordoes not registerJavaTimeModuleunder Jackson 3.Written for commit 00b3413. Summary will update on new commits.