OpenAPITools / OpenAPITools/openapi-generator

[BUG][JAVA] oneOf/anyOf with discriminator generates model that picks only one subtype payload, ignoring others

Open
#23,276 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

[BUG][JAVA] oneOf/anyOf with discriminator generates model that picks only one subtype payload, ignoring others

Description

When using oneOf (or anyOf) with a discriminator on the payload property of a parent schema, the Java code generator produces a single model class that only represents one of the sub-schemas instead of properly generating a polymorphic type with all variants.

Scenario: I have a DeviceLifecycleEvent with a payload that can be either DeviceDataTransferEventPayload or DeviceCommModeStatusEventPayload, determined by a type discriminator field.

Expected behavior: The generator should produce:

  • A parent/interface type for the payload (e.g., DeviceLifecycleEventPayload) with @JsonTypeInfo and @JsonSubTypes annotations
  • Two concrete subtype classes (DeviceDataTransferEventPayload and DeviceCommModeStatusEventPayload)

Actual behavior: The generator produces a single DeviceLifecycleEventPayload class that contains properties from only one of the two schemas (whichever is listed first or last), effectively ignoring the other variant entirely. No polymorphic annotations (@JsonTypeInfo/@JsonSubTypes) are generated.

This makes oneOf/anyOf with discriminator unusable for generating proper polymorphic Java models from OpenAPI 3.0.3 specs.

openapi-generator version

7.12.0 (via openapi-generator-maven-plugin)

OpenAPI declaration file content or url
openapi: 3.0.3
info:
  version: v1
  title: Device service events
paths: {}
components:
  schemas:
    DeviceLifecycleEvent:
      type: object
      description: Notification about device lifecycle events
      required:
        - type
        - header
        - payload
      properties:
        type:
          type: string
          enum:
            - device_datatransfer_status_changed
            - device_communication_mode_status_changed
        header:
          $ref: "#/components/schemas/DeviceMessageHeader"
        payload:
          oneOf:
            - $ref: "#/components/schemas/DeviceDataTransferEventPayload"
            - $ref: "#/components/schemas/DeviceCommModeStatusEventPayload"
          discriminator:
            propertyName: type
            mapping:
              device_datatransfer_status_changed: "#/components/schemas/DeviceDataTransferEventPayload"
              device_communication_mode_status_changed: "#/components/schemas/DeviceCommModeStatusEventPayload"

    DeviceMessageHeader:
      type: object
      required:
        - ts
        - correlationId
        - deviceId
      properties:
        correlationId:
          type: string
        deviceId:
          type: string
        ts:
          type: string
          format: date-time

    DeviceDataTransferEventPayload:
      type: object
      required:
        - deviceId
        - serialNumber
        - updatedAt
        - eventData
      properties:
        deviceId:
          type: string
        serialNumber:
          type: string
        updatedAt:
          type: string
        eventData:
          $ref: "#/components/schemas/DeviceDataTransferEventData"

    DeviceCommModeStatusEventPayload:
      type: object
      required:
        - deviceId
        - serialNumber
        - updatedAt
        - eventData
      properties:
        deviceId:
          type: string
        serialNumber:
          type: string
        updatedAt:
          type: string
        eventData:
          $ref: "#/components/schemas/DeviceCommModeStatusEventData"

    DeviceDataTransferEventData:
      type: object
      required:
        - StatusDetails
      properties:
        StatusDetails:
          type: object
          properties:
            dataTransfer:
              type: object
              properties:
                status:
                  type: string
                  enum: [capable, incapable, Not Applicable]
                statusReason:
                  type: string
                updatedAt:
                  type: string
            isMuted:
              type: boolean

    DeviceCommModeStatusEventData:
      type: object
      required:
        - StatusDetails
      properties:
        StatusDetails:
          type: object
          properties:
            communicationMode:
              type: object
              properties:
                runtimeCommMode:
                  type: string
                  enum: [limited, normal]
                updatedAt:
                  type: string
Generation Details

Maven plugin configuration:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.12.0</version>
    <executions>
        <execution>
            <id>device-events</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/yaml/device-messages.yml</inputSpec>
                <generatorName>java</generatorName>
                <library>feign</library>
                <generateModels>true</generateModels>
                <generateModelTests>false</generateModelTests>
                <generateModelDocumentation>false</generateModelDocumentation>
                <generateApis>false</generateApis>
                <generateApiTests>false</generateApiTests>
                <generateApiDocumentation>false</generateApiDocumentation>
                <generateSupportingFiles>false</generateSupportingFiles>
                <packageName>com.gehc.sto.device.event</packageName>
                <modelPackage>com.gehc.sto.device.event</modelPackage>
                <configOptions>
                    <openApiNullable>false</openApiNullable>
                    <interfaceOnly>true</interfaceOnly>
                    <useJakartaEe>true</useJakartaEe>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

Equivalent CLI command:

openapi-generator-cli generate \
  -i device-messages.yml \
  -g java \
  --library feign \
  --model-package com.example.event \
  -p openApiNullable=false,interfaceOnly=true,useJakartaEe=true \
  --global-property models
Steps to reproduce
  1. Create an OpenAPI 3.0.3 spec with a schema that uses oneOf (or anyOf) on an inline property with a discriminator and mapping (see YAML above)
  2. Generate Java models using openapi-generator-maven-plugin v7.12.0 with java generator and feign library
  3. Inspect the generated DeviceLifecycleEventPayload.java

Observed result:

  • DeviceLifecycleEventPayload.java is generated as a concrete class with fields from only one of the two payload schemas (e.g., only DeviceCommModeStatusEventPayload fields)
  • No @JsonTypeInfo or @JsonSubTypes annotations are present
  • The other payload type's unique fields are completely missing
  • Effectively, one of the two oneOf variants is silently dropped

Expected result:

  • Either a parent abstract class or interface DeviceLifecycleEventPayload with @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") and @JsonSubTypes listing both concrete types
  • Or proper generation of both concrete types that can be used polymorphically

Note: Using anyOf instead of oneOf produces the same broken result — a merged/flattened class that picks properties from only one variant.

Workaround that works: Using allOf with a discriminator on a shared parent schema generates correct polymorphic classes with @JsonTypeInfo + @JsonSubTypes. However, this changes the semantic meaning of the schema (inheritance vs. composition) and removes the payload property from the parent schema in documentation tools (Swagger UI).

Related issues/PRs
Suggest a fix

The Java generator's oneOf/anyOf handling for inline properties (not top-level schemas) with a discriminator should:

  1. Generate a parent interface or abstract class (e.g., DeviceLifecycleEventPayload) annotated with @JsonTypeInfo and @JsonSubTypes
  2. Each oneOf/anyOf variant should be generated as a concrete class implementing/extending that parent
  3. The discriminator mapping should drive the @JsonSubTypes.Type annotations

Currently, the generator seems to merge/flatten the oneOf variants into a single class, which defeats the purpose of oneOf with discriminator entirely. The allOf + discriminator path already handles this correctly for top-level schema inheritance — similar logic should be applied to inline oneOf/anyOf property discriminators.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the issue with the supplied device-messages.yml using the Java generator and inspect the generated DeviceLifecycleEventPayload.java. Compare the inline oneOf/anyOf output with the documented allOf workaround and related discriminator issues. Done means both payload variants are retained and polymorphic annotations are generated, with regression coverage if the repository's generator tests cover this path.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi
Domain
backend-api-design, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.