Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/samples-kotlin-client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

- samples/client/petstore/kotlin-model-prefix-type-mappings
# needs Android configured
#- samples/client/petstore/kotlin-json-request-string
Expand Down
10 changes: 10 additions & 0 deletions bin/configs/kotlin-jackson3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-jackson3
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
serializationLibrary: jackson
useJackson3: "true"
artifactId: kotlin-petstore-jackson3
enumPropertyNaming: UPPERCASE
enumUnknownDefaultCase: "true"
10 changes: 10 additions & 0 deletions bin/configs/kotlin-jvm-spring-4-restclient-jackson3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
generatorName: kotlin
outputDir: samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3
Copy link
Member

@wing328 wing328 Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add this new folder to the workflow file as well (samples-kotlin-client.yaml)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yap, I moved it from draft just to trigger the review - thanks for the comment!

library: jvm-spring-restclient
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
artifactId: kotlin-petstore-spring4-restclient-jackson3
enumUnknownDefaultCase: "true"
serializationLibrary: jackson
useSpringBoot4: "true"
3 changes: 2 additions & 1 deletion docs/generators/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sourceFolder|source folder for generated code| |src/main/kotlin|
|supportAndroidApiLevel25AndBelow|[WARNING] This flag will generate code that has a known security vulnerability. It uses `kotlin.io.createTempFile` instead of `java.nio.file.Files.createTempFile` in order to support Android API level 25 and below. For more info, please check the following links https://github.com/OpenAPITools/openapi-generator/security/advisories/GHSA-23x4-m842-fmwf, https://github.com/OpenAPITools/openapi-generator/pull/9284| |false|
|useCoroutines|Whether to use the Coroutines adapter with the retrofit2 library.| |false|
|useJackson3|Use Jackson 3 dependencies (tools.jackson package). Not yet supported for kotlin-client; reserved for future use.| |false|
|useJackson3|Use Jackson 3 dependencies (tools.jackson package). Requires serializationLibrary=jackson. Incompatible with openApiNullable.| |false|
|useNonAsciiHeaders|Allow to use non-ascii headers with the okhttp library| |false|
|useResponseAsReturnType|When using retrofit2 and coroutines, use `Response`&lt;`T`&gt; as return type instead of `T`.| |true|
|useRxJava3|Whether to use the RxJava3 adapter with the retrofit2 library.| |false|
|useSettingsGradle|Whether the project uses settings.gradle.| |false|
|useSpringBoot3|Whether to use the Spring Boot 3 with the jvm-spring-webclient library.| |false|
|useSpringBoot4|Use Spring Boot 4 with the jvm-spring-restclient or jvm-spring-webclient library. Implies useJackson3.| |false|

## SUPPORTED VENDOR EXTENSIONS

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
public static final String USE_SETTINGS_GRADLE = "useSettingsGradle";
public static final String IDEA = "idea";
public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
public static final String USE_SPRING_BOOT4 = "useSpringBoot4";
public static final String USE_RESPONSE_AS_RETURN_TYPE = "useResponseAsReturnType";

public static final String DATE_LIBRARY = "dateLibrary";
Expand Down Expand Up @@ -271,6 +272,7 @@ public KotlinClientCodegen() {
cliOptions.add(CliOption.newBoolean(USE_RX_JAVA3, "Whether to use the RxJava3 adapter with the retrofit2 library."));
cliOptions.add(CliOption.newBoolean(USE_COROUTINES, "Whether to use the Coroutines adapter with the retrofit2 library."));
cliOptions.add(CliOption.newBoolean(USE_SPRING_BOOT3, "Whether to use the Spring Boot 3 with the jvm-spring-webclient library."));
cliOptions.add(CliOption.newBoolean(USE_SPRING_BOOT4, "Use Spring Boot 4 with the jvm-spring-restclient or jvm-spring-webclient library. Implies useJackson3."));
cliOptions.add(CliOption.newBoolean(OMIT_GRADLE_PLUGIN_VERSIONS, "Whether to declare Gradle plugin versions in build files."));
cliOptions.add(CliOption.newBoolean(OMIT_GRADLE_WRAPPER, "Whether to omit Gradle wrapper for creating a sub project."));
cliOptions.add(CliOption.newBoolean(USE_SETTINGS_GRADLE, "Whether the project uses settings.gradle."));
Expand Down Expand Up @@ -300,7 +302,7 @@ public KotlinClientCodegen() {
cliOptions.add(CliOption.newBoolean(USE_RESPONSE_AS_RETURN_TYPE, "When using retrofit2 and coroutines, use `Response`<`T`> as return type instead of `T`.", true));

cliOptions.add(CliOption.newBoolean(USE_JACKSON_3,
"Use Jackson 3 dependencies (tools.jackson package). Not yet supported for kotlin-client; reserved for future use."));
"Use Jackson 3 dependencies (tools.jackson package). Requires serializationLibrary=jackson. Incompatible with openApiNullable."));
}

@Override
Expand Down Expand Up @@ -469,9 +471,10 @@ public void processOpts() {
convertPropertyToBooleanAndWriteBack(USE_SPRING_BOOT3);
}

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");
setUseJackson3(true);
Comment on lines +474 to +477
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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);
}
Fix with Cubic

}

if (additionalProperties.containsKey(CodegenConstants.SERIALIZATION_LIBRARY)) {
Expand All @@ -481,6 +484,16 @@ public void processOpts() {
additionalProperties.put(this.serializationLibrary.name(), true);
}

if (isUseJackson3()) {
if (this.serializationLibrary != SERIALIZATION_LIBRARY_TYPE.jackson) {
throw new IllegalArgumentException("useJackson3 requires serializationLibrary=jackson");
}
if (additionalProperties.containsKey("openApiNullable")
&& Boolean.parseBoolean(additionalProperties.get("openApiNullable").toString())) {
throw new IllegalArgumentException("openApiNullable cannot be set with useJackson3");
}
}

if (additionalProperties.containsKey(MAP_FILE_BINARY_TO_BYTE_ARRAY)) {
setMapFileBinaryToByteArray(convertPropertyToBooleanAndWriteBack(MAP_FILE_BINARY_TO_BYTE_ARRAY));
}
Expand Down Expand Up @@ -864,8 +877,9 @@ private void processJvmSpringWebClientLibrary(final String infrastructureFolder)
}

private void processJvmSpringRestClientLibrary(final String infrastructureFolder) {
if (additionalProperties.getOrDefault(USE_SPRING_BOOT3, false).equals(false)) {
throw new RuntimeException("This library must use Spring Boot 3. Try adding '--additional-properties useSpringBoot3=true' to your command.");
if (additionalProperties.getOrDefault(USE_SPRING_BOOT3, false).equals(false)
&& additionalProperties.getOrDefault(USE_SPRING_BOOT4, false).equals(false)) {
throw new RuntimeException("This library requires Spring Boot 3 or 4. Try adding '--additional-properties useSpringBoot3=true' or '--additional-properties useSpringBoot4=true' to your command.");
}

processJvmSpring(infrastructureFolder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ buildscript {
ext.vertx_version = "5.0.4"
{{/jvm-vertx}}
{{#jvm-spring}}
{{#useSpringBoot4}}
ext.spring_boot_version = "4.0.1"
{{/useSpringBoot4}}
{{^useSpringBoot4}}
{{#useSpringBoot3}}
ext.spring_boot_version = "3.5.5"
{{/useSpringBoot3}}
{{^useSpringBoot3}}
ext.spring_boot_version = "2.7.18"
{{/useSpringBoot3}}
{{/useSpringBoot4}}
{{/jvm-spring}}
ext.spotless_version = "7.2.1"

Expand Down Expand Up @@ -163,8 +168,13 @@ dependencies {
{{/gson}}
{{#jackson}}
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
{{#useJackson3}}
implementation "tools.jackson.module:jackson-module-kotlin:3.0.1"
{{/useJackson3}}
{{^useJackson3}}
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.20.0"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.0"
{{/useJackson3}}
{{/jackson}}
{{#kotlinx_serialization}}
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,24 @@ import kotlinx.datetime.LocalTime
import java.util.UUID
{{/gson}}
{{#jackson}}
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
{{^useJackson3}}
import {{jacksonPackage}}.databind.DeserializationFeature
import {{jacksonPackage}}.databind.ObjectMapper
import {{jacksonPackage}}.databind.SerializationFeature
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import {{jacksonPackage}}.module.kotlin.jacksonObjectMapper
{{/useJackson3}}
{{#useJackson3}}
import {{jacksonPackage}}.databind.DeserializationFeature
import {{jacksonPackage}}.databind.ObjectMapper
import {{jacksonPackage}}.databind.cfg.DateTimeFeature
{{#enumUnknownDefaultCase}}
import {{jacksonPackage}}.databind.cfg.EnumFeature
{{/enumUnknownDefaultCase}}
import com.fasterxml.jackson.annotation.JsonInclude
import {{jacksonPackage}}.module.kotlin.jsonMapper
import {{jacksonPackage}}.module.kotlin.kotlinModule
{{/useJackson3}}
{{/jackson}}
{{#kotlinx_serialization}}
import java.math.BigDecimal
Expand Down Expand Up @@ -120,6 +133,7 @@ import java.util.concurrent.atomic.AtomicLong
}
{{/gson}}
{{#jackson}}
{{^useJackson3}}
@JvmStatic
val jacksonObjectMapper: ObjectMapper = jacksonObjectMapper()
.findAndRegisterModules()
Expand All @@ -129,6 +143,24 @@ import java.util.concurrent.atomic.AtomicLong
{{/enumUnknownDefaultCase}}
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}})
{{/useJackson3}}
{{#useJackson3}}
@JvmStatic
val jacksonObjectMapper: ObjectMapper = jsonMapper {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the type of jacksonObjectMapper be specified as JsonMapper instead of ObjectMapper?

Especially if it is to be used with in the generated code for the client, which passes it to a JacksonJsonHttpMessageConverter that takes in JsonMapper as parameter.

This is my generated code with the snapshot of this PR:
Image

addModule(kotlinModule())
changeDefaultPropertyInclusion { it.withValueInclusion(JsonInclude.Include.NON_ABSENT) }
{{#enumUnknownDefaultCase}}
enable(EnumFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
{{/enumUnknownDefaultCase}}
disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

{{#failOnUnknownProperties}}
enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
{{/failOnUnknownProperties}}
{{^failOnUnknownProperties}}
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
{{/failOnUnknownProperties}}
}
{{/useJackson3}}
{{/jackson}}
{{#kotlinx_serialization}}
private var isAdaptersInitialized = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import com.google.gson.GsonBuilder
import java.text.DateFormat
{{/gson}}
{{#jackson}}
import com.fasterxml.jackson.databind.ObjectMapper
import {{jacksonPackage}}.databind.ObjectMapper
{{/jackson}}

{{#operations}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import java.text.DateFormat
{{/gson}}
{{#jackson}}
import io.ktor.serialization.jackson.*
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.core.util.DefaultIndenter
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
import {{jacksonPackage}}.module.kotlin.jacksonObjectMapper
import {{jacksonPackage}}.databind.ObjectMapper
import {{jacksonPackage}}.databind.SerializationFeature
{{^useJackson3}}
import {{jacksonPackage}}.datatype.jsr310.JavaTimeModule
{{/useJackson3}}
import {{jacksonPackage}}.core.util.DefaultIndenter
import {{jacksonPackage}}.core.util.DefaultPrettyPrinter
{{/jackson}}
import {{packageName}}.auth.*

Expand Down Expand Up @@ -96,7 +98,9 @@ import {{packageName}}.auth.*
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
indentObjectsWith(DefaultIndenter(" ", "\n"))
})
{{^useJackson3}}
registerModule(JavaTimeModule())
{{/useJackson3}}
}
{{/jackson}}
protected val UNSAFE_HEADERS: List<String> = listOf(HttpHeaders.ContentType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import org.threeten.bp.OffsetTime
import com.google.gson.reflect.TypeToken
{{/gson}}
{{#jackson}}
import com.fasterxml.jackson.core.type.TypeReference
import {{jacksonPackage}}.core.type.TypeReference
{{/jackson}}
{{#moshi}}
import com.squareup.moshi.adapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import com.squareup.moshi.Moshi
import retrofit2.converter.moshi.MoshiConverterFactory
{{/moshi}}
{{#jackson}}
import com.fasterxml.jackson.databind.ObjectMapper
import {{jacksonPackage}}.databind.ObjectMapper
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

import retrofit2.converter.jackson.JacksonConverterFactory
{{/jackson}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import org.springframework.web.client.RestClient
import org.springframework.web.client.RestClientResponseException

{{#jackson}}
{{^useJackson3}}
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
{{/useJackson3}}
{{#useJackson3}}
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter
import {{packageName}}.infrastructure.Serializer
{{/useJackson3}}
{{/jackson}}
import org.springframework.http.ResponseEntity
import org.springframework.http.MediaType
Expand All @@ -23,11 +29,20 @@ import {{packageName}}.infrastructure.*
{{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}open {{/nonPublicApi}}class {{classname}}(client: RestClient) : ApiClient(client) {

{{#jackson}}
{{^useJackson3}}
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}constructor(baseUrl: String) : this(RestClient.builder()
.baseUrl(baseUrl)
.messageConverters { it.add(MappingJackson2HttpMessageConverter()) }
.build()
)
{{/useJackson3}}
{{#useJackson3}}
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}constructor(baseUrl: String) : this(RestClient.builder()
.baseUrl(baseUrl)
.configureMessageConverters { it.withJsonConverter(JacksonJsonHttpMessageConverter(Serializer.jacksonObjectMapper)) }
.build()
)
{{/useJackson3}}
{{/jackson}}

{{#operation}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import java.io.IOException

{{#jackson}}
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.type.TypeReference
import {{jacksonPackage}}.core.type.TypeReference
{{/jackson}}
{{#gson}}
import com.google.gson.reflect.TypeToken
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import io.vertx.core.Vertx
import io.vertx.core.buffer.Buffer
import java.nio.charset.StandardCharsets
{{#jackson}}
import com.fasterxml.jackson.core.type.TypeReference
import {{jacksonPackage}}.core.type.TypeReference
{{/jackson}}
{{#gson}}
import com.google.gson.reflect.TypeToken
Expand Down
Loading
Loading