Skip to content

Conversation

@alexhumphreys
Copy link

@alexhumphreys alexhumphreys commented Jan 30, 2026

Description

Fixes #16457

Fixes two bugs in the Kotlin jvm-okhttp4 client when handling multipart/form-data requests with parts that have Content-Type: application/json.

Developed with assistance from Claude Code (Sonnet 3.5)

Problem 1: IllegalArgumentException

When a multipart part includes Content-Type in its headers (via OpenAPI encoding declarations), OkHttp throws:

IllegalArgumentException: Unexpected header: Content-Type

Root cause: OkHttp's MultipartBody API requires Content-Type to be passed via the asRequestBody(mediaType) or toRequestBody(mediaType) parameter, not in the headers map passed to addPart().

Problem 2: Invalid JSON Serialization

When a part has Content-Type: application/json, the object is serialized using Kotlin's toString() instead of proper JSON serialization.

Example of broken output:

Content-Disposition: form-data; name="metadata"
Content-Type: application/json

FileMetadata(id=12345, name=test-file, tags=[tag1, tag2])

Expected output:

Content-Disposition: form-data; name="metadata"
Content-Type: application/json

{"id":12345,"name":"test-file","tags":["tag1","tag2"]}

Solution

Modified the addPartToMultiPart() helper methods in ApiClient.kt.mustache to:

  1. Filter Content-Type from headers before passing to OkHttp
  2. Inspect per-part Content-Type and use appropriate serialization:
    • If Content-Type contains "json": use JSON serializer (gson/moshi/jackson/kotlinx)
    • Otherwise: use parameterToString() as before
  3. Pass Content-Type via mediaType parameter to toRequestBody()/asRequestBody()

The fix supports all serialization libraries: gson, moshi, jackson, and kotlinx-serialization.

Testing

Added comprehensive integration tests:

  1. New test spec: modules/openapi-generator/src/test/resources/3_0/kotlin/echo_multipart_json.yaml

    • Defines multipart endpoint with JSON metadata + file upload
    • Uses encoding to declare Content-Types
  2. New sample client: samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/

    • Generated from the test spec
    • Includes MultipartJsonTest.kt with tests that:
      • Verify JSON is serialized properly (not toString)
      • Verify no IllegalArgumentException is thrown
      • Test against real echo server (returns request body for inspection)
  3. CI integration: Updated .github/workflows/samples-kotlin-echo-api.yaml to run the new tests

Test Results

Before fix:

IllegalArgumentException at MultipartJsonTest.kt:23
IllegalArgumentException at MultipartJsonTest.kt:52

After fix:

BUILD SUCCESSFUL

Regression check: Existing kotlin-jvm-okhttp tests still pass ✅

Use Case

This fix is essential for REST APIs that combine file uploads with structured metadata, such as:

  • Document management systems (PDF + metadata)
  • Image/media uploads (photo + EXIF/tags)
  • Form submissions with attachments

cc @jimschubert (Kotlin maintainer)

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR solves a reported issue, reference it using GitHub's linking syntax
  • If your PR is targeting a particular programming language, @mention the technical committee members.

Summary by cubic

Fixes multipart/form-data handling in the Kotlin jvm-okhttp4 client when a part uses Content-Type: application/json. Prevents OkHttp header errors and serializes JSON parts as real JSON instead of toString().

  • Bug Fixes
    • Remove Content-Type from part headers and pass it via mediaType to toRequestBody/asRequestBody.
    • Detect application/json per part and serialize with the configured JSON library (gson/moshi/jackson/kotlinx); otherwise use parameterToString().
    • Add echo-based multipart JSON test spec and sample; enable in CI workflow.

Written for commit 4e1607e. Summary will update on new commits.

Fixes OpenAPITools#16457

Fixes two critical bugs in multipart/form-data handling when parts
have Content-Type application/json:

1. IllegalArgumentException: OkHttp throws "Unexpected header: Content-Type"
   because Content-Type was passed in headers map instead of via
   asRequestBody(mediaType)/toRequestBody(mediaType) parameter.

2. Invalid JSON serialization: Non-file parts with application/json
   Content-Type were serialized using toString() instead of proper
   JSON serialization, producing invalid output like:
   "MyObject(field1=value, field2=123)" instead of
   '{"field1":"value","field2":123}'

Changes:
- Filter Content-Type from headers before passing to OkHttp
- Check part Content-Type and use appropriate serializer (JSON vs toString)
- Add integration tests with echo server to verify fix
- Support all serialization libraries (gson, moshi, jackson, kotlinx)

Fixes issues with multipart endpoints that mix file uploads with
JSON metadata, common in REST APIs for document/image uploads.
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

26 issues found across 62 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/docs/BodyApi.md">

<violation number="1" location="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/docs/BodyApi.md:25">
P3: The Kotlin example declares `metadata` with an `=` but provides no value, producing invalid code that users cannot compile or run.</violation>

<violation number="2" location="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/docs/BodyApi.md:40">
P3: Malformed Markdown table: header/separator rows come after the first data row, so `metadata` is rendered outside the table.</violation>
</file>

<file name="samples/client/petstore/kotlin-jvm-okhttp4-coroutines/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-jvm-okhttp4-coroutines/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:106">
P2: Multipart file parts drop explicit Content-Type from headers and never pass it to asRequestBody, so spec-defined media types are lost in favor of guessed types.</violation>
</file>

<file name="samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Explicit Content-Type header for file parts is now filtered out and never applied to the RequestBody, so callers cannot override the guessed media type.</violation>
</file>

<file name="samples/client/petstore/kotlin-array-simple-string-jvm-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-array-simple-string-jvm-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Multipart file parts ignore an explicit Content-Type header and always fall back to guessing from the file extension, which can override the caller’s intended media type.</violation>
</file>

<file name="samples/client/petstore/kotlin-explicit/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-explicit/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Explicit Content-Type for file parts is discarded; the file overload always guesses media type from filename, ignoring user/API-provided Content-Type headers.</violation>
</file>

<file name="samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: File multipart parts drop the caller-provided Content-Type and always guess from filename, so explicit part Content-Types (e.g., from spec encoding) are ignored.</violation>
</file>

<file name="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt">

<violation number="1" location="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt:15">
P2: ByteArray is serialized via String(value)/toByteArray() using the platform default charset, which is lossy for arbitrary binary data and can corrupt non‑text bytes. ByteArray JSON should be base64‑encoded to preserve data.</violation>
</file>

<file name="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt">

<violation number="1" location="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt:8">
P2: collectionDelimiter handles "pipe"/"space" but standard OpenAPI collectionFormat values are "pipes" and "ssv", causing no delimiter and malformed serialized arrays.</violation>
</file>

<file name="samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-gson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: File multipart parts ignore any user-specified Content-Type header; the header is stripped but the media type is always guessed from the filename. This can send the wrong part Content-Type when the OpenAPI spec or caller specifies it explicitly.</violation>
</file>

<file name="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:105">
P2: File multipart parts ignore an explicit Content-Type header and always use a guessed media type, so OpenAPI-specified content types for file parts can be lost.</violation>
</file>

<file name="samples/client/petstore/kotlin-enum-default-value/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-enum-default-value/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Multipart file parts now drop any caller-provided Content-Type and always use extension-based guessing, which ignores explicit MIME types from the OpenAPI spec or user headers.</violation>
</file>

<file name="samples/client/petstore/kotlin-modelMutable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-modelMutable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: File multipart parts drop a provided Content-Type header and always rely on extension-based guessing, so spec-defined part media types are lost.</violation>
</file>

<file name="samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-nonpublic/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Explicit Content-Type headers for multipart file parts are discarded after filtering, so the file part always uses the guessed media type instead of the caller-specified one.</violation>
</file>

<file name="samples/client/others/kotlin-jvm-okhttp-path-comments/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/others/kotlin-jvm-okhttp-path-comments/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: File multipart parts discard an explicit Content-Type header and always guess from the filename, so user-provided content types are ignored for file uploads.</violation>
</file>

<file name="samples/client/petstore/kotlin-allOf-discriminator-kotlinx-serialization/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-allOf-discriminator-kotlinx-serialization/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:104">
P2: Explicit Content-Type header for multipart File parts is ignored; media type is always guessed from file extension, so per-part content types in headers (e.g., from OpenAPI encoding) are discarded.</violation>
</file>

<file name="samples/client/others/kotlin-jvm-okhttp-non-ascii-headers/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/others/kotlin-jvm-okhttp-non-ascii-headers/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Multipart file parts ignore explicit Content-Type headers; only filename-based guessing is used, so caller/spec-provided part content types are lost.</violation>
</file>

<file name="samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-jackson/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: File multipart parts discard an explicit Content-Type header and always use a guessed media type from the filename, so content types defined in part headers are ignored.</violation>
</file>

<file name="samples/client/petstore/kotlin-bigdecimal-default-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-bigdecimal-default-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Explicit Content-Type headers for multipart file parts are stripped and never used; the file media type is always guessed from the filename, so provided content types are ignored.</violation>
</file>

<file name="samples/client/petstore/kotlin-name-parameter-mappings/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-name-parameter-mappings/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Explicit Content-Type for file parts is stripped from headers but never applied to the RequestBody, so multipart file uploads always use filename-based guessing and ignore spec-defined media types.</violation>
</file>

<file name="samples/client/others/kotlin-integer-enum/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/others/kotlin-integer-enum/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: User-provided `Content-Type` for file multipart parts is stripped and ignored; file media type is always guessed from filename, so spec-defined content types are lost.</violation>
</file>

<file name="samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Multipart file parts discard explicit Content-Type headers but never use that value for the RequestBody media type, so caller-specified part content types are ignored and the file may be sent as application/octet-stream.</violation>
</file>

<file name="samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Explicit `Content-Type` for file parts is discarded: the file overload filters it out and always uses filename guessing, so user-specified part content types are ignored.</violation>
</file>

<file name="samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:103">
P2: Multipart file parts ignore an explicit Content-Type header, always using a filename guess instead. This can send incorrect media types for file uploads when the OpenAPI spec provides a specific Content-Type.</violation>
</file>

<file name="samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt">

<violation number="1" location="samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt:105">
P2: File multipart parts discard explicit Content-Type headers and always use a guessed media type, so per‑part content types configured in headers are ignored.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/infrastructure/ApiClient.kt.mustache:170">
P2: Multipart JSON parts using Kotlinx Serialization will throw at runtime because `encodeToString(obj)` is invoked with type‑erased `Any` (no serializer), which requires polymorphic registration.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

val partHeaders = headers.toMutableMap() +
// Filter out Content-Type from headers as OkHttp requires it to be passed
// separately via asRequestBody(mediaType), not in the headers map
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P2: Multipart file parts drop explicit Content-Type from headers and never pass it to asRequestBody, so spec-defined media types are lost in favor of guessed types.

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-okhttp4-coroutines/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt, line 106:

<comment>Multipart file parts drop explicit Content-Type from headers and never pass it to asRequestBody, so spec-defined media types are lost in favor of guessed types.</comment>

<file context>
@@ -101,7 +101,9 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
-        val partHeaders = headers.toMutableMap() +
+        // Filter out Content-Type from headers as OkHttp requires it to be passed
+        // separately via asRequestBody(mediaType), not in the headers map
+        val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
             ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
         val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
</file context>
Fix with Cubic

val partHeaders = headers.toMutableMap() +
// Filter out Content-Type from headers as OkHttp requires it to be passed
// separately via asRequestBody(mediaType), not in the headers map
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P2: Explicit Content-Type header for file parts is now filtered out and never applied to the RequestBody, so callers cannot override the guessed media type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-moshi-codegen/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt, line 103:

<comment>Explicit Content-Type header for file parts is now filtered out and never applied to the RequestBody, so callers cannot override the guessed media type.</comment>

<file context>
@@ -98,7 +98,9 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
-        val partHeaders = headers.toMutableMap() +
+        // Filter out Content-Type from headers as OkHttp requires it to be passed
+        // separately via asRequestBody(mediaType), not in the headers map
+        val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
             ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
         val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
</file context>
Fix with Cubic

val partHeaders = headers.toMutableMap() +
// Filter out Content-Type from headers as OkHttp requires it to be passed
// separately via asRequestBody(mediaType), not in the headers map
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P2: Multipart file parts ignore an explicit Content-Type header and always fall back to guessing from the file extension, which can override the caller’s intended media type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-array-simple-string-jvm-okhttp4/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt, line 103:

<comment>Multipart file parts ignore an explicit Content-Type header and always fall back to guessing from the file extension, which can override the caller’s intended media type.</comment>

<file context>
@@ -98,7 +98,9 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
-        val partHeaders = headers.toMutableMap() +
+        // Filter out Content-Type from headers as OkHttp requires it to be passed
+        // separately via asRequestBody(mediaType), not in the headers map
+        val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
             ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
         val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
</file context>
Fix with Cubic

val partHeaders = headers.toMutableMap() +
// Filter out Content-Type from headers as OkHttp requires it to be passed
// separately via asRequestBody(mediaType), not in the headers map
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P2: Explicit Content-Type for file parts is discarded; the file overload always guesses media type from filename, ignoring user/API-provided Content-Type headers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-explicit/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt, line 103:

<comment>Explicit Content-Type for file parts is discarded; the file overload always guesses media type from filename, ignoring user/API-provided Content-Type headers.</comment>

<file context>
@@ -98,7 +98,9 @@ public open class ApiClient(public val baseUrl: String, public val client: Call.
-        val partHeaders = headers.toMutableMap() +
+        // Filter out Content-Type from headers as OkHttp requires it to be passed
+        // separately via asRequestBody(mediaType), not in the headers map
+        val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
             ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
         val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
</file context>
Fix with Cubic

val partHeaders = headers.toMutableMap() +
// Filter out Content-Type from headers as OkHttp requires it to be passed
// separately via asRequestBody(mediaType), not in the headers map
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P2: File multipart parts drop the caller-provided Content-Type and always guess from filename, so explicit part Content-Types (e.g., from spec encoding) are ignored.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt, line 103:

<comment>File multipart parts drop the caller-provided Content-Type and always guess from filename, so explicit part Content-Types (e.g., from spec encoding) are ignored.</comment>

<file context>
@@ -98,7 +98,9 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
-        val partHeaders = headers.toMutableMap() +
+        // Filter out Content-Type from headers as OkHttp requires it to be passed
+        // separately via asRequestBody(mediaType), not in the headers map
+        val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
             ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
         val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
</file context>
Fix with Cubic

val partHeaders = headers.toMutableMap() +
// Filter out Content-Type from headers as OkHttp requires it to be passed
// separately via asRequestBody(mediaType), not in the headers map
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P2: Multipart file parts ignore an explicit Content-Type header, always using a filename guess instead. This can send incorrect media types for file uploads when the OpenAPI spec provides a specific Content-Type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-nullable/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt, line 103:

<comment>Multipart file parts ignore an explicit Content-Type header, always using a filename guess instead. This can send incorrect media types for file uploads when the OpenAPI spec provides a specific Content-Type.</comment>

<file context>
@@ -98,7 +98,9 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
-        val partHeaders = headers.toMutableMap() +
+        // Filter out Content-Type from headers as OkHttp requires it to be passed
+        // separately via asRequestBody(mediaType), not in the headers map
+        val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
             ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
         val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
</file context>
Fix with Cubic

Comment on lines +105 to 107
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P2: File multipart parts discard explicit Content-Type headers and always use a guessed media type, so per‑part content types configured in headers are ignored.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/kotlin-json-request-string/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt, line 105:

<comment>File multipart parts discard explicit Content-Type headers and always use a guessed media type, so per‑part content types configured in headers are ignored.</comment>

<file context>
@@ -100,7 +100,9 @@ open class ApiClient(val baseUrl: String, val client: Call.Factory = defaultClie
-        val partHeaders = headers.toMutableMap() +
+        // Filter out Content-Type from headers as OkHttp requires it to be passed
+        // separately via asRequestBody(mediaType), not in the headers map
+        val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
             ("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
         val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
</file context>
Suggested change
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
val partContentType = headers["Content-Type"]
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
val fileMediaType = (partContentType ?: guessContentTypeFromFile(file)).toMediaTypeOrNull()
Fix with Cubic

Copy link
Author

@alexhumphreys alexhumphreys Jan 30, 2026

Choose a reason for hiding this comment

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

The "always guessing" behavior is a separate pre-existing issue, not introduced by this PR. As to whether this suggested change of (partContentType ?: guessContentTypeFromFile(file)) is correct, I'm not sure. I think it might need it's own investigation and PR.

This remark covers the various duplicate comments below.

Serializer.jacksonObjectMapper.writeValueAsString(obj)
{{/jackson}}
{{#kotlinx_serialization}}
Serializer.kotlinxSerializationJson.encodeToString(obj)
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P2: Multipart JSON parts using Kotlinx Serialization will throw at runtime because encodeToString(obj) is invoked with type‑erased Any (no serializer), which requires polymorphic registration.

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-okhttp/infrastructure/ApiClient.kt.mustache, line 170:

<comment>Multipart JSON parts using Kotlinx Serialization will throw at runtime because `encodeToString(obj)` is invoked with type‑erased `Any` (no serializer), which requires polymorphic registration.</comment>

<file context>
@@ -148,11 +150,31 @@ import com.squareup.moshi.adapter
+            Serializer.jacksonObjectMapper.writeValueAsString(obj)
+            {{/jackson}}
+            {{#kotlinx_serialization}}
+            Serializer.kotlinxSerializationJson.encodeToString(obj)
+            {{/kotlinx_serialization}}
+        } else {
</file context>
Fix with Cubic

//import org.openapitools.client.models.*

val apiInstance = BodyApi()
val metadata : FileMetadata = // FileMetadata |
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P3: The Kotlin example declares metadata with an = but provides no value, producing invalid code that users cannot compile or run.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/docs/BodyApi.md, line 25:

<comment>The Kotlin example declares `metadata` with an `=` but provides no value, producing invalid code that users cannot compile or run.</comment>

<file context>
@@ -0,0 +1,57 @@
+//import org.openapitools.client.models.*
+
+val apiInstance = BodyApi()
+val metadata : FileMetadata =  // FileMetadata | 
+val file : java.io.File = BINARY_DATA_HERE // java.io.File | File to upload
+try {
</file context>
Fix with Cubic

```

### Parameters
| **metadata** | [**FileMetadata**](FileMetadata.md)| | |
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 30, 2026

Choose a reason for hiding this comment

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

P3: Malformed Markdown table: header/separator rows come after the first data row, so metadata is rendered outside the table.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/docs/BodyApi.md, line 40:

<comment>Malformed Markdown table: header/separator rows come after the first data row, so `metadata` is rendered outside the table.</comment>

<file context>
@@ -0,0 +1,57 @@
+```
+
+### Parameters
+| **metadata** | [**FileMetadata**](FileMetadata.md)|  | |
+| Name | Type | Description  | Notes |
+| ------------- | ------------- | ------------- | ------------- |
</file context>
Fix with Cubic

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.

[BUG] [kotlin] [okhttp4] generator for kotlin client with okhttp4 doesn't correctly serialize object part for multipart/form-data

1 participant