Skip to content

[java][Native] [#12911] Improve handling of oneOf/anyOf with mixed object and lists etc - #25268

Open
duttonw wants to merge 2 commits into
OpenAPITools:masterfrom
duttonw:12911_java_native_handle_oneof_item_and_list_mix
Open

duttonw wants to merge 2 commits into
OpenAPITools:masterfrom
duttonw:12911_java_native_handle_oneof_item_and_list_mix

Conversation

@duttonw

@duttonw duttonw commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
  • Improve handling of oneOf/anyOf with mixed object and lists etc (open since 2022)
  • Also correct any tests outside of native changes (unrelated but boy scout rule, don't leave broken tests in master)
  • Also updated native pom to use current versions for items.
  • Had to also correct javadoc issues where < and > symbols were not html escaped once oneOf/anyOf tests were added which also contained lists.
  • Also jackson3 is usually always going to go with newest Jakarta spec (not javax) from 17+ so i flipped the tests for it.

I had to skip adding the public constructors for List in oneOf/anyOf due to inability to only add it once and causing java compile errors (you can't have more than on interface of List...)

java native can now handle

    fruitOrFruits:
      properties:
        color:
          type: string
      x-has-list-alternative: true
      oneOf:
        - $ref: "#/components/schemas/apple"
        - type: array
          items:
            $ref: "#/components/schemas/apple"
        - $ref: "#/components/schemas/banana"
        - type: array
          items:
            $ref: "#/components/schemas/banana"
      additionalProperties: false
    gmFruitOrFruits:
      properties:
        color:
          type: string
      x-has-list-alternative: true
      anyOf:
        - $ref: "#/components/schemas/apple"
        - type: array
          items:
            $ref: "#/components/schemas/apple"
        - $ref: "#/components/schemas/banana"
        - type: array
          items:
            $ref: "#/components/schemas/banana"
      additionalProperties: false

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

@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)


Summary by cubic

Improves the Java native client's handling of oneOf/anyOf schemas that mix object and list alternatives. Deserialization now inspects the JSON shape before each candidate, so list alternatives are only attempted on arrays and object alternatives only on objects.

  • Added fruitOrFruits (oneOf) and gmFruitOrFruits (anyOf) test schemas covering mixed object/list alternatives.
  • Public constructors for list alternatives are not generated because Java cannot implement multiple List interface variants in one class.
  • Updated Jackson and Jakarta dependency versions in the native templates, regenerated samples, and switched the Jackson 3 sample to Jakarta EE annotations.
  • Generated oneOf/anyOf docs now iterate composedSchemas for correct ordering and escape </> in javadoc.
  • Removed a duplicate jakarta.validation dependency from the retrofit2 POM.

Written for commit dfe2f8c. Summary will update on new commits.

View guided diff

…ith mixed object and lists etc

Also correct any tests outside of native changes (unrelated but boyscout rule, don't leave broken tests in master)

@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.

19 issues found across 154 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/client/petstore/java/native/docs/FruitOrFruits.md">

<violation number="1" location="samples/client/petstore/java/native/docs/FruitOrFruits.md:17">
P2: The generated examples treat `List<Apple>` as a model class, producing nonexistent documentation links and invalid Java imports and identifiers such as `exampleList<Apple>`. Use `java.util.List` with a concrete implementation and valid variable names, or omit list examples when no public list constructor exists.</violation>

<violation number="2" location="samples/client/petstore/java/native/docs/FruitOrFruits.md:39">
P2: This list example is not valid Java: `exampleList<Apple>` is not a legal identifier, and `List` cannot be instantiated; the imports also refer to nonexistent model classes. Use `java.util.List` with `ArrayList` and legal variable names throughout the example.</violation>
</file>

<file name="samples/client/petstore/java/native-async/src/main/java/org/openapitools/client/model/FruitOrFruits.java">

<violation number="1" location="samples/client/petstore/java/native-async/src/main/java/org/openapitools/client/model/FruitOrFruits.java:416">
P2: This treats every list alternative as `List<Apple>`; a non-empty `List<Banana>` throws `ClassCastException` when an element is read, and `List<Banana>` query serialization is missing. Handle both list element types here.</violation>
</file>

<file name="samples/client/petstore/java/native/src/main/java/org/openapitools/client/model/FruitOrFruits.java">

<violation number="1" location="samples/client/petstore/java/native/src/main/java/org/openapitools/client/model/FruitOrFruits.java:303">
P2: Checking only `List.class` also accepts values such as `List<String>` as valid alternatives, and the serializer then emits them as though they matched this oneOf. Validate list elements against the allowed Apple and Banana list schemas before accepting the value.</violation>

<violation number="2" location="samples/client/petstore/java/native/src/main/java/org/openapitools/client/model/FruitOrFruits.java:418">
P2: Serializing a `List<Banana>` through `toUrlQueryString` enters the `List<Apple>` branch and casts each element to Apple. Distinguish list alternatives before casting elements, or serialize the actual list elements without an incorrect generic cast.</violation>
</file>

<file name="samples/client/petstore/java/native/src/main/java/org/openapitools/client/model/GmFruitOrFruits.java">

<violation number="1" location="samples/client/petstore/java/native/src/main/java/org/openapitools/client/model/GmFruitOrFruits.java:149">
P2: The `apple` alternative is nullable, so `null` is valid for this `anyOf`, but this override rejects it unconditionally. Return `null` for this model's null value.</violation>

<violation number="2" location="samples/client/petstore/java/native/src/main/java/org/openapitools/client/model/GmFruitOrFruits.java:312">
P2: `GmFruitOrFruits.toUrlQueryString` always returns null, dropping query parameters for every populated anyOf value. Generate its query branches from the anyOf alternatives instead of leaving the method with only the fallback return.</violation>
</file>

<file name="samples/client/petstore/java/native/docs/GmFruitOrFruits.md">

<violation number="1" location="samples/client/petstore/java/native/docs/GmFruitOrFruits.md:39">
P2: The list examples are not valid Java: `List<Apple>` is imported as a model, `List` is instantiated, and `exampleList<Apple>` is not a legal identifier. Render these alternatives as `List` values backed by `ArrayList`, with valid variable names and imports.</violation>
</file>

<file name="samples/client/petstore/java/native-jackson3/src/main/java/org/openapitools/client/model/FruitOrFruits.java">

<violation number="1" location="samples/client/petstore/java/native-jackson3/src/main/java/org/openapitools/client/model/FruitOrFruits.java:416">
P2: This branch treats every accepted list as `List<Apple>`, so `toUrlQueryString()` throws `ClassCastException` for a non-empty `List<Banana>`. Handle Banana elements and emit the corresponding list alternative as well.</violation>
</file>

<file name="samples/client/petstore/java/native-jackson3/src/test/java/org/openapitools/client/model/FruitOrFruitsTest.java">

<violation number="1" location="samples/client/petstore/java/native-jackson3/src/test/java/org/openapitools/client/model/FruitOrFruitsTest.java:41">
P2: `testFruitOrFruits` is a no-op, so this suite never verifies the new model's object-versus-array deserialization; add assertions for both JSON shapes.</violation>
</file>

<file name="samples/client/petstore/java/native/src/test/java/org/openapitools/client/model/FruitOrFruitsTest.java">

<violation number="1" location="samples/client/petstore/java/native/src/test/java/org/openapitools/client/model/FruitOrFruitsTest.java:41">
P2: This test always passes without exercising the mixed object/list deserializer. Add assertions for object and array inputs and verify the selected `getActualInstance()` type.</violation>
</file>

<file name="samples/client/petstore/java/native-async/docs/GmFruitOrFruits.md">

<violation number="1" location="samples/client/petstore/java/native-async/docs/GmFruitOrFruits.md:39">
P2: The list-alternative example cannot compile: its `List<...>` imports and variable names are invalid, and `List` cannot be instantiated. Use `java.util.List` with `ArrayList` and valid variable names.</violation>
</file>

<file name="samples/client/petstore/java/native-async/src/test/java/org/openapitools/client/model/FruitOrFruitsTest.java">

<violation number="1" location="samples/client/petstore/java/native-async/src/test/java/org/openapitools/client/model/FruitOrFruitsTest.java:41">
P2: This test class never exercises `FruitOrFruits`, so the new object-versus-list deserialization behavior can regress while the test suite stays green. Add assertions covering both object and array alternatives.</violation>
</file>

<file name="samples/client/petstore/java/native-async/docs/FruitOrFruits.md">

<violation number="1" location="samples/client/petstore/java/native-async/docs/FruitOrFruits.md:17">
P2: This Java example cannot compile: generic types cannot appear in import declarations, `List` is an interface, and names such as `exampleList<Apple>` are invalid identifiers. Use `java.util.List` with `ArrayList` instances and valid variable names throughout the list alternatives.</violation>
</file>

<file name="samples/client/petstore/java/native-jackson3/docs/FruitOrFruits.md">

<violation number="1" location="samples/client/petstore/java/native-jackson3/docs/FruitOrFruits.md:8">
P2: These links target documentation pages that do not exist. Render the array alternatives as unlinked code text instead.</violation>

<violation number="2" location="samples/client/petstore/java/native-jackson3/docs/FruitOrFruits.md:17">
P2: The sample cannot compile: this import names a nonexistent class, and `exampleList<Apple>` is not a legal identifier. Use `java.util.List`/`ArrayList` and legal variable names for the array alternatives.</violation>
</file>

<file name="samples/client/petstore/java/native-jackson3/docs/GmFruitOrFruits.md">

<violation number="1" location="samples/client/petstore/java/native-jackson3/docs/GmFruitOrFruits.md:17">
P2: This code example cannot compile: `List<Apple>` is not an importable model, `exampleList<Apple>` is an invalid identifier, and `List` cannot be instantiated. Import `java.util.List`, use a concrete implementation, and give the variables ordinary names.</violation>
</file>

<file name="samples/client/petstore/java/native/src/main/java/org/openapitools/client/model/Triangle.java">

<violation number="1" location="samples/client/petstore/java/native/src/main/java/org/openapitools/client/model/Triangle.java:107">
P2: These Triangle alternatives are POJOs, so they bypass both container branches and the scalar branch, leaving `attemptParsing` true for every JSON token. After fixing the `TypeReference` call, arrays can still reach model deserializers and produce extra matches under custom mapper settings; gate POJO candidates on `START_OBJECT`.</violation>
</file>

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

<violation number="1" location="modules/openapi-generator/src/main/resources/Java/libraries/native/anyof_model.mustache:93">
P1: The anyOf deserializer still attempts every alternative for every JSON shape. Gate list alternatives on `START_ARRAY` and object alternatives on `START_OBJECT` before binding to prevent false or ambiguous matches.</violation>
</file>

Reply to a comment to ask cubic a question or push back. It learns from your replies.

View guided diff | Re-trigger cubic

return;
}

if (JSON.isInstanceOf(java.util.List.class, instance, new HashSet<Class<?>>())) {

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: Checking only List.class also accepts values such as List<String> as valid alternatives, and the serializer then emits them as though they matched this oneOf. Validate list elements against the allowed Apple and Banana list schemas before accepting the value.

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/native/src/main/java/org/openapitools/client/model/FruitOrFruits.java, line 303:

<comment>Checking only `List.class` also accepts values such as `List<String>` as valid alternatives, and the serializer then emits them as though they matched this oneOf. Validate list elements against the allowed Apple and Banana list schemas before accepting the value.</comment>

<file context>
@@ -0,0 +1,439 @@
+            return;
+        }
+
+        if (JSON.isInstanceOf(java.util.List.class, instance, new HashSet<Class<?>>())) {
+            super.setActualInstance(instance);
+            return;
</file context>

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.

this is sadly a thing of needing to unwrap to inject which could be done but yeah. more work is required and this was to just pass compiling issues. (testing in anger is not complete)...

final Class<?> candidateClass = candidateType.getRawClass();
boolean attemptParsing = true;
// containers (List/Set/Map) must match the JSON shape; element types are checked while binding
if (candidateType.isCollectionLikeType()) {

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: These Triangle alternatives are POJOs, so they bypass both container branches and the scalar branch, leaving attemptParsing true for every JSON token. After fixing the TypeReference call, arrays can still reach model deserializers and produce extra matches under custom mapper settings; gate POJO candidates on START_OBJECT.

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/native/src/main/java/org/openapitools/client/model/Triangle.java, line 107:

<comment>These Triangle alternatives are POJOs, so they bypass both container branches and the scalar branch, leaving `attemptParsing` true for every JSON token. After fixing the `TypeReference` call, arrays can still reach model deserializers and produce extra matches under custom mapper settings; gate POJO candidates on `START_OBJECT`.</comment>

<file context>
@@ -99,19 +99,27 @@ public Triangle deserialize(JsonParser jp, DeserializationContext ctxt) throws I
+                final Class<?> candidateClass = candidateType.getRawClass();
                 boolean attemptParsing = true;
+                // containers (List/Set/Map) must match the JSON shape; element types are checked while binding
+                if (candidateType.isCollectionLikeType()) {
+                    attemptParsing = token == JsonToken.START_ARRAY;
+                } else if (candidateType.isMapLikeType()) {
</file context>

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.

uncertain, the reason the need for the 'class' wrapping is to not crash out on list which does not compile.

Comment thread samples/client/petstore/java/native/docs/FruitOrFruits.md Outdated
*/
@Test
void testFruitOrFruits() {
// TODO: test FruitOrFruits

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: testFruitOrFruits is a no-op, so this suite never verifies the new model's object-versus-array deserialization; add assertions for both JSON shapes.

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/native-jackson3/src/test/java/org/openapitools/client/model/FruitOrFruitsTest.java, line 41:

<comment>`testFruitOrFruits` is a no-op, so this suite never verifies the new model's object-versus-array deserialization; add assertions for both JSON shapes.</comment>

<file context>
@@ -0,0 +1,68 @@
+     */
+    @Test
+    void testFruitOrFruits() {
+        // TODO: test FruitOrFruits
+    }
+
</file context>

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.

this code was not altered so this is a pre-existing bug. marking as (not my changes) -- other than the new object struct that induced this message

Comment thread samples/client/petstore/java/native-async/docs/FruitOrFruits.md Outdated

@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.

1 issue found across 61 files (changes from recent commits).

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="modules/openapi-generator/src/main/resources/Java/libraries/native/model_oneof_doc.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/Java/libraries/native/model_oneof_doc.mustache:9">
P3: Nested container alternatives still produce broken documentation links: `items.dataType` unwraps only one level, so `List<List<Apple>>` links to `List<Apple>.md`, which is not generated. Resolve the innermost model type recursively or omit the link for non-model containers.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

* [{{{.}}}]({{{.}}}.md)
{{/oneOf}}
{{#composedSchemas.oneOf}}
* [{{dataType}}]({{#isArray}}{{#uniqueItems}}{{items.dataType}}{{/uniqueItems}}{{^uniqueItems}}{{items.dataType}}{{/uniqueItems}}{{/isArray}}{{^isArray}}{{#isMap}}{{items.dataType}}{{/isMap}}{{^isMap}}{{dataType}}{{/isMap}}{{/isArray}}.md)

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: Nested container alternatives still produce broken documentation links: items.dataType unwraps only one level, so List<List<Apple>> links to List<Apple>.md, which is not generated. Resolve the innermost model type recursively or omit the link for non-model containers.

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/native/model_oneof_doc.mustache, line 9:

<comment>Nested container alternatives still produce broken documentation links: `items.dataType` unwraps only one level, so `List<List<Apple>>` links to `List<Apple>.md`, which is not generated. Resolve the innermost model type recursively or omit the link for non-model containers.</comment>

<file context>
@@ -5,9 +5,9 @@
-* [{{{.}}}]({{{.}}}.md)
-{{/oneOf}}
+{{#composedSchemas.oneOf}}
+* [{{dataType}}]({{#isArray}}{{#uniqueItems}}{{items.dataType}}{{/uniqueItems}}{{^uniqueItems}}{{items.dataType}}{{/uniqueItems}}{{/isArray}}{{^isArray}}{{#isMap}}{{items.dataType}}{{/isMap}}{{^isMap}}{{dataType}}{{/isMap}}{{/isArray}}.md)
+{{/composedSchemas.oneOf}}
 
</file context>

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.

Majority of the time, this won't be needed since it would normally be then wrapped in a named object schema. (or can be restructured to be like so)

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.

1 participant