Skip to content

fix(java): serialize jersey multipart object parts as JSON and honor … - #25120

Open
MatteoMolinari93 wants to merge 2 commits into
OpenAPITools:masterfrom
MatteoMolinari93:fix-jersey-multipart
Open

MatteoMolinari93 wants to merge 2 commits into
OpenAPITools:masterfrom
MatteoMolinari93:fix-jersey-multipart

Conversation

@MatteoMolinari93

@MatteoMolinari93 MatteoMolinari93 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Closes #25119

@bbdouglas (2017/07) @sreeshas (2017/08) @jfiala (2017/08) @lukoyanov (2017/09) @cbornet (2017/09) @jeff9finger (2018/01) @karismann (2019/03) @Zomzog (2019/04) @lwlee2608 (2019/10) @martin-mfg (2023/08) @KannaKim (2026/07)

PR checklist

  • Read the contribution guidelines.
  • 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.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Fixes Jersey multipart/form-data serialization so complex parts (objects, arrays) are sent as JSON instead of plain strings, and per-part contentType declared in the spec's encoding is honored.

  • serialize gains an overload accepting per-part content types; the existing signature delegates with an empty map, so callers are unaffected.
  • Generated API templates populate content types from the request body encoding declarations.
  • Adds tests covering JSON-serialized complex parts and encoding-based content types, including a new multipart-encoding.yaml spec.

Written for commit 9ced467. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

18 issues found across 31 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="samples/openapi3/client/petstore/java/jersey2-java8-special-characters/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/openapi3/client/petstore/java/jersey2-java8-special-characters/src/main/java/org/openapitools/client/ApiClient.java:916">
P2: The complex-object fallback ignores the declared multipart content type and always emits `application/json`. Preserve the JSON serialization if intended, but use `partType` when it is declared so per-part `encoding.contentType` is honored.</violation>
</file>

<file name="samples/openapi3/client/petstore/java/jersey2-java8-swagger2/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/openapi3/client/petstore/java/jersey2-java8-swagger2/src/main/java/org/openapitools/client/ApiClient.java:1041">
P2: This branch overrides a declared non-JSON part content type for complex values and sends `Content-Type: application/json`, so the new encoding metadata is not honored for those valid multipart parts. Preserve the declared `partType` when it is present, using JSON only as the fallback when no part type was declared.</violation>
</file>

<file name="samples/client/petstore/java/jersey3-jackson3/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/client/petstore/java/jersey3-jackson3/src/main/java/org/openapitools/client/ApiClient.java:1140">
P2: This fallback discards a declared non-JSON `partType` for complex values, so the multipart part advertises `application/json` instead of the encoding's type. Use the declared type when present and JSON only when no type was declared.</violation>

<violation number="2" location="samples/client/petstore/java/jersey3-jackson3/src/main/java/org/openapitools/client/ApiClient.java:1140">
P2: When the encoding declares a non-JSON per-part content type for a complex object part (e.g. `application/xml` or `text/csv`), this branch ignores `partType` entirely and always sends the part with `Content-Type: application/json`. The declared encoding is therefore not honored on the wire, and the part header silently contradicts the spec. Either apply the declared `partType` when it is supported, or throw an `ApiException` for declared types the serializer cannot produce, instead of mislabeling the part as JSON.</violation>
</file>

<file name="samples/client/others/java/jersey2-oneOf-Mixed/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/client/others/java/jersey2-oneOf-Mixed/src/main/java/org/openapitools/client/ApiClient.java:916">
P2: This fallback discards a declared non-JSON `partType` for complex values and always labels the JSON text as `application/json`. Preserve `partType` when it is present, using `application/json` only when no type was declared.</violation>
</file>

<file name="samples/client/petstore/java/jersey3-oneOf/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/client/petstore/java/jersey3-oneOf/src/main/java/org/openapitools/client/ApiClient.java:917">
P1: This branch ignores the declared content type for complex multipart values, so `encoding` cannot be honored for non-JSON types. Keep JSON serialization if needed, but pass the non-null `partType` to `FormDataBodyPart` instead of always using `MediaType.APPLICATION_JSON_TYPE`.</violation>
</file>

<file name="samples/client/petstore/java/jersey2-java8-localdatetime/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/client/petstore/java/jersey2-java8-localdatetime/src/main/java/org/openapitools/client/ApiClient.java:1057">
P2: The complex-value fallback ignores a declared non-JSON `partType` and sends the part as `application/json`, so multipart encodings such as `application/xml` are not honored. Use the declared `partType` for the body part when present, while retaining JSON as the fallback only when it is absent.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/Java/libraries/jersey3/ApiClient.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/Java/libraries/jersey3/ApiClient.mustache:1146">
P2: Complex multipart values discard a declared non-JSON encoding and are always labeled `application/json`. Preserve `partType` here, falling back to `application/json` only when no type was declared.</violation>
</file>

<file name="samples/client/petstore/java/jersey3/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/client/petstore/java/jersey3/src/main/java/org/openapitools/client/ApiClient.java:1140">
P1: Complex multipart values discard the declared `partType`: an encoding such as `application/xml` is serialized as JSON but emitted as `Content-Type: application/json`. Preserve the declared type here so servers parse the part according to the OpenAPI encoding.</violation>
</file>

<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java:5554">
P3: The ApiClient assertions in this test only check text emitted unconditionally by ApiClient.mustache, so they pass for any jersey spec; the only spec-dependent assertion is that `marker` is added to the form params. As written, the test would still pass if a regression serialized the `marker` object via `parameterToString` instead of JSON, so the claim in the comment (`complex parts must be sent as JSON, not via toString()`) is not actually verified. Add a runtime assertion for the object part (e.g., a Jersey client test that sends `marker` and asserts the part is JSON) or narrow the comment to what the static checks actually pin down.</violation>
</file>

<file name="samples/openapi3/client/extensions/x-auth-id-alias/java/jersey2-java8/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/openapi3/client/extensions/x-auth-id-alias/java/jersey2-java8/src/main/java/org/openapitools/client/ApiClient.java:965">
P2: Complex multipart values ignore a declared non-JSON content type and are always sent as `application/json`. Preserve the declared `partType` for this branch (while retaining JSON as the default when no type is declared).</violation>
</file>

<file name="samples/openapi3/client/petstore/java/jersey2-java8-swagger1/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/openapi3/client/petstore/java/jersey2-java8-swagger1/src/main/java/org/openapitools/client/ApiClient.java:1041">
P2: This fallback discards a declared non-JSON encoding for complex values and labels the JSON text as `application/json`. Preserve the declared `partType` when present, using `application/json` only when no type was declared.</violation>
</file>

<file name="samples/openapi3/client/petstore/java/jersey2-java8/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/openapi3/client/petstore/java/jersey2-java8/src/main/java/org/openapitools/client/ApiClient.java:1139">
P2: `addParamToMultipart` discards the declared `partType` for complex values and hardcodes `application/json`. Preserve the declared media type here, or fail explicitly when the value cannot be serialized for that type.</violation>

<violation number="2" location="samples/openapi3/client/petstore/java/jersey2-java8/src/main/java/org/openapitools/client/ApiClient.java:1139">
P3: For complex objects the declared per-part content type is discarded: this branch hardcodes `MediaType.APPLICATION_JSON_TYPE` even when `partType` was parsed from `encoding` and is not a JSON type (e.g. `text/plain`, `application/xml`). That contradicts the new javadoc on `addParamToMultipart` ("The declared part type … takes precedence") and means encoding-based content types are only honored for scalars, files, and JSON types. If the intent is to always send complex parts as JSON, use the declared type only when it is JSON, or apply `partType` as done for the other branches.</violation>
</file>

<file name="samples/client/others/java/jersey2-oneOf-duplicates/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/client/others/java/jersey2-oneOf-duplicates/src/main/java/org/openapitools/client/ApiClient.java:916">
P2: This branch ignores the declared content type for complex parts. An object or array encoded with a non-JSON type is sent with `application/json`, so the server cannot honor the OpenAPI multipart encoding; retain the declared type when present and use JSON only as the default.</violation>
</file>

<file name="samples/client/petstore/java/jersey2-java8/src/main/java/org/openapitools/client/ApiClient.java">

<violation number="1" location="samples/client/petstore/java/jersey2-java8/src/main/java/org/openapitools/client/ApiClient.java:1057">
P2: The complex-value fallback discards the declared `partType`, so multipart encodings such as `text/plain` or `application/xml` still receive `application/json`. Preserve the declared type for this branch, while retaining JSON as the fallback when no type is declared.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache:1140">
P2: This fallback ignores a declared non-JSON `partType`, so complex multipart values are always labeled `application/json` even when the request-body encoding specifies another content type. Use the declared type when present and retain `application/json` only as the fallback.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache:1140">
P3: In the complex-object branch of `addParamToMultipart`, a declared non-JSON part content type is silently replaced with `application/json`. A complex part with `encoding: { field: { contentType: text/xml } }` (or `application/x-custom`) is emitted as a JSON body tagged `application/json`, so the declared per-part content type from `encoding` is not honored — which contradicts the PR's stated goal. Use the declared type when present and only fall back to `application/json` when `partType` is null.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: This branch ignores the declared content type for complex multipart values, so encoding cannot be honored for non-JSON types. Keep JSON serialization if needed, but pass the non-null partType to FormDataBodyPart instead of always using MediaType.APPLICATION_JSON_TYPE.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/client/petstore/java/jersey3-oneOf/src/main/java/org/openapitools/client/ApiClient.java, line 917:

<comment>This branch ignores the declared content type for complex multipart values, so `encoding` cannot be honored for non-JSON types. Keep JSON serialization if needed, but pass the non-null `partType` to `FormDataBodyPart` instead of always using `MediaType.APPLICATION_JSON_TYPE`.</comment>

<file context>
@@ -843,27 +864,124 @@ public Entity<?> serialize(Object obj, Map<String, Object> formParams, String co
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
     }
+  }
</file context>
Suggested change
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), partType == null ? MediaType.APPLICATION_JSON_TYPE : partType));

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Complex multipart values discard the declared partType: an encoding such as application/xml is serialized as JSON but emitted as Content-Type: application/json. Preserve the declared type here so servers parse the part according to the OpenAPI encoding.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/client/petstore/java/jersey3/src/main/java/org/openapitools/client/ApiClient.java, line 1140:

<comment>Complex multipart values discard the declared `partType`: an encoding such as `application/xml` is serialized as JSON but emitted as `Content-Type: application/json`. Preserve the declared type here so servers parse the part according to the OpenAPI encoding.</comment>

<file context>
@@ -1066,27 +1087,124 @@ public Entity<?> serialize(Object obj, Map<String, Object> formParams, String co
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
+    }
+  }
</file context>
Suggested change
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), partType == null ? MediaType.APPLICATION_JSON_TYPE : partType));

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The complex-object fallback ignores the declared multipart content type and always emits application/json. Preserve the JSON serialization if intended, but use partType when it is declared so per-part encoding.contentType is honored.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/openapi3/client/petstore/java/jersey2-java8-special-characters/src/main/java/org/openapitools/client/ApiClient.java, line 916:

<comment>The complex-object fallback ignores the declared multipart content type and always emits `application/json`. Preserve the JSON serialization if intended, but use `partType` when it is declared so per-part `encoding.contentType` is honored.</comment>

<file context>
@@ -839,30 +860,127 @@ public Entity<?> serialize(Object obj, Map<String, Object> formParams, String co
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
+    }
+  }
</file context>
Suggested change
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), partType == null ? MediaType.APPLICATION_JSON_TYPE : partType));

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This branch overrides a declared non-JSON part content type for complex values and sends Content-Type: application/json, so the new encoding metadata is not honored for those valid multipart parts. Preserve the declared partType when it is present, using JSON only as the fallback when no part type was declared.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/openapi3/client/petstore/java/jersey2-java8-swagger2/src/main/java/org/openapitools/client/ApiClient.java, line 1041:

<comment>This branch overrides a declared non-JSON part content type for complex values and sends `Content-Type: application/json`, so the new encoding metadata is not honored for those valid multipart parts. Preserve the declared `partType` when it is present, using JSON only as the fallback when no part type was declared.</comment>

<file context>
@@ -964,30 +985,127 @@ public Entity<?> serialize(Object obj, Map<String, Object> formParams, String co
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
+    }
+  }
</file context>
Suggested change
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), partType == null ? MediaType.APPLICATION_JSON_TYPE : partType));

@MatteoMolinari93 MatteoMolinari93 Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not applying the suggestion. This branch always produces JSON via Jackson. The jersey clients have no XML/CSV/other serializer for multipart parts, so labelling the part with a declared non-JSON type (e.g. application/xml) would make the header contradict the bytes on the wire. JSON-family types (application/json, *+json) are already honoured by the isJsonPartType branch, and files and plain values use the declared type. Throwing instead would turn requests that work today into errors, so I'd rather not do that in this PR. The javadoc wrongly claimed the declared type "takes precedence"; it now describes the actual behaviour, and the branch has a comment explaining why. The same applies to the generated samples.

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This fallback discards a declared non-JSON partType for complex values, so the multipart part advertises application/json instead of the encoding's type. Use the declared type when present and JSON only when no type was declared.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/client/petstore/java/jersey3-jackson3/src/main/java/org/openapitools/client/ApiClient.java, line 1140:

<comment>This fallback discards a declared non-JSON `partType` for complex values, so the multipart part advertises `application/json` instead of the encoding's type. Use the declared type when present and JSON only when no type was declared.</comment>

<file context>
@@ -1066,27 +1087,124 @@ public Entity<?> serialize(Object obj, Map<String, Object> formParams, String co
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
+    }
+  }
</file context>
Suggested change
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), partType == null ? MediaType.APPLICATION_JSON_TYPE : partType));

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This fallback ignores a declared non-JSON partType, so complex multipart values are always labeled application/json even when the request-body encoding specifies another content type. Use the declared type when present and retain application/json only as the fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache, line 1140:

<comment>This fallback ignores a declared non-JSON `partType`, so complex multipart values are always labeled `application/json` even when the request-body encoding specifies another content type. Use the declared type when present and retain `application/json` only as the fallback.</comment>

<file context>
@@ -1063,30 +1084,127 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
+    }
+  }
</file context>
Suggested change
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), partType == null ? MediaType.APPLICATION_JSON_TYPE : partType));

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When the encoding declares a non-JSON per-part content type for a complex object part (e.g. application/xml or text/csv), this branch ignores partType entirely and always sends the part with Content-Type: application/json. The declared encoding is therefore not honored on the wire, and the part header silently contradicts the spec. Either apply the declared partType when it is supported, or throw an ApiException for declared types the serializer cannot produce, instead of mislabeling the part as JSON.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/client/petstore/java/jersey3-jackson3/src/main/java/org/openapitools/client/ApiClient.java, line 1140:

<comment>When the encoding declares a non-JSON per-part content type for a complex object part (e.g. `application/xml` or `text/csv`), this branch ignores `partType` entirely and always sends the part with `Content-Type: application/json`. The declared encoding is therefore not honored on the wire, and the part header silently contradicts the spec. Either apply the declared `partType` when it is supported, or throw an `ApiException` for declared types the serializer cannot produce, instead of mislabeling the part as JSON.</comment>

<file context>
@@ -1066,27 +1087,124 @@ public Entity<?> serialize(Object obj, Map<String, Object> formParams, String co
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
+    }
+  }
</file context>

validateJavaSourceFiles(files);
assertThat(output.resolve("src/main/java/xyz/abcdef/api/MultipartApi.java")).content()
.contains("localVarFormParams.put(\"marker\", marker);");
// complex parts must be sent as JSON, not via toString()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The ApiClient assertions in this test only check text emitted unconditionally by ApiClient.mustache, so they pass for any jersey spec; the only spec-dependent assertion is that marker is added to the form params. As written, the test would still pass if a regression serialized the marker object via parameterToString instead of JSON, so the claim in the comment (complex parts must be sent as JSON, not via toString()) is not actually verified. Add a runtime assertion for the object part (e.g., a Jersey client test that sends marker and asserts the part is JSON) or narrow the comment to what the static checks actually pin down.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java, line 5554:

<comment>The ApiClient assertions in this test only check text emitted unconditionally by ApiClient.mustache, so they pass for any jersey spec; the only spec-dependent assertion is that `marker` is added to the form params. As written, the test would still pass if a regression serialized the `marker` object via `parameterToString` instead of JSON, so the claim in the comment (`complex parts must be sent as JSON, not via toString()`) is not actually verified. Add a runtime assertion for the object part (e.g., a Jersey client test that sends `marker` and asserts the part is JSON) or narrow the comment to what the static checks actually pin down.</comment>

<file context>
@@ -5535,6 +5535,62 @@ public void testArrayPathParamSerializedAsCollection(String library) {
+        validateJavaSourceFiles(files);
+        assertThat(output.resolve("src/main/java/xyz/abcdef/api/MultipartApi.java")).content()
+                .contains("localVarFormParams.put(\"marker\", marker);");
+        // complex parts must be sent as JSON, not via toString()
+        assertThat(output.resolve("src/main/java/xyz/abcdef/invoker/ApiClient.java")).content()
+                .contains("} else if (isPlainFormValue(value)) {")
</file context>

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: In the complex-object branch of addParamToMultipart, a declared non-JSON part content type is silently replaced with application/json. A complex part with encoding: { field: { contentType: text/xml } } (or application/x-custom) is emitted as a JSON body tagged application/json, so the declared per-part content type from encoding is not honored — which contradicts the PR's stated goal. Use the declared type when present and only fall back to application/json when partType is null.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache, line 1140:

<comment>In the complex-object branch of `addParamToMultipart`, a declared non-JSON part content type is silently replaced with `application/json`. A complex part with `encoding: { field: { contentType: text/xml } }` (or `application/x-custom`) is emitted as a JSON body tagged `application/json`, so the declared per-part content type from `encoding` is not honored — which contradicts the PR's stated goal. Use the declared type when present and only fall back to `application/json` when `partType` is null.</comment>

<file context>
@@ -1063,30 +1084,127 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
+    }
+  }
</file context>
Suggested change
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), partType == null ? MediaType.APPLICATION_JSON_TYPE : partType));

// Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: For complex objects the declared per-part content type is discarded: this branch hardcodes MediaType.APPLICATION_JSON_TYPE even when partType was parsed from encoding and is not a JSON type (e.g. text/plain, application/xml). That contradicts the new javadoc on addParamToMultipart ("The declared part type … takes precedence") and means encoding-based content types are only honored for scalars, files, and JSON types. If the intent is to always send complex parts as JSON, use the declared type only when it is JSON, or apply partType as done for the other branches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At samples/openapi3/client/petstore/java/jersey2-java8/src/main/java/org/openapitools/client/ApiClient.java, line 1139:

<comment>For complex objects the declared per-part content type is discarded: this branch hardcodes `MediaType.APPLICATION_JSON_TYPE` even when `partType` was parsed from `encoding` and is not a JSON type (e.g. `text/plain`, `application/xml`). That contradicts the new javadoc on `addParamToMultipart` ("The declared part type … takes precedence") and means encoding-based content types are only honored for scalars, files, and JSON types. If the intent is to always send complex parts as JSON, use the declared type only when it is JSON, or apply `partType` as done for the other branches.</comment>

<file context>
@@ -1062,30 +1083,127 @@ public Entity<?> serialize(Object obj, Map<String, Object> formParams, String co
+      // Complex objects (models, maps, ...) are sent as JSON parts instead of their toString() output
       FormDataContentDisposition contentDisp = FormDataContentDisposition.name(key).build();
-      multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(value)));
+      multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
+    }
+  }
</file context>
Suggested change
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), MediaType.APPLICATION_JSON_TYPE));
multiPart.bodyPart(new FormDataBodyPart(contentDisp, serializeToJson(value), partType == null ? MediaType.APPLICATION_JSON_TYPE : partType));

This branch has not been deployed

No deployments
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][JAVA][jersey2/jersey3] multipart/form-data: object parts sent as toString() text instead of JSON, and encoding.contentType ignored

1 participant