Skip to content

Java SDK generation fails on discriminator values null, true, and false #17429

Description

@ChoMinGi

Java SDK generation fails on discriminator values null, true, and false

When a discriminated union's discriminator value is the literal string "null"—or equivalently "true" / "false"—the Java generator aborts with:

IllegalArgumentException: not a valid name: null

Reproduction

The following minimal OpenAPI schema reproduces the failure against the current Java generator:

openapi: 3.0.3
info:
  title: Java discriminator literal repro
  version: 1.0.0

paths:
  /filter:
    post:
      operationId: createFilter
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Filter"
      responses:
        "204":
          description: OK

components:
  schemas:
    Filter:
      oneOf:
        - $ref: "#/components/schemas/StringFilter"
        - $ref: "#/components/schemas/NullFilter"
      discriminator:
        propertyName: type
        mapping:
          string: "#/components/schemas/StringFilter"
          "null": "#/components/schemas/NullFilter"

    StringFilter:
      type: object
      properties:
        type:
          type: string
          enum: ["string"]
        value:
          type: string
      required: [type, value]

    NullFilter:
      type: object
      properties:
        type:
          type: string
          enum: ["null"]
        field:
          type: string
      required: [type, field]

Running fern ir confirms that the IR correctly preserves:

"discriminantValue": "null"

The failure is therefore downstream of OpenAPI parsing, in Java identifier generation.

Expected behavior is to generate a safe Java identifier such as null_(...) while preserving "null" as the serialized discriminator value.

Root cause

CasingConfiguration.JAVA_RESERVED_KEYWORDS appends _ when a generated Java identifier conflicts with a reserved token. This convention already works for ordinary Java keywords—for example, the existing SipHeaderAction fixture under seed/java-sdk/reserved-keywords generates static_(...) while retaining @JsonTypeName("static") for the wire value.

The fallback set omits the three Java literals:

null
true
false

They are not classified as JLS keywords, but none of them is a valid Java identifier. As a result:

computeName("null").camelSafe

currently evaluates to:

null

instead of:

null_

UnionGenerator then passes null to JavaPoet as a method name, and JavaPoet correctly rejects it.

KeyWordUtils.java, a separate reserved-token list in the same Java generator, already contains null, true, and false. This appears to be drift between the two lists rather than an intentional difference.

Verification

I verified the failure end-to-end and at each relevant boundary:

  • Ran fern generate --local against the minimal schema above using the published fernapi/fern-java-sdk:4.19.0 Docker image. Generation aborts with:

    Exception in thread "main" java.lang.RuntimeException:
    java.lang.IllegalArgumentException: not a valid name: null
        at com.fern.java.AbstractGeneratorCli.run(AbstractGeneratorCli.java:412)
        at com.fern.java.client.Cli.main(Cli.java:122)
    Caused by: java.lang.IllegalArgumentException: not a valid name: null
        at com.squareup.javapoet.MethodSpec.methodBuilder(MethodSpec.java:188)
        at com.fern.java.generators.UnionGenerator$ModelUnionSubTypes
            .getStaticFactory(UnionGenerator.java:440)
        at com.fern.java.generators.union.UnionTypeSpecGenerator
            .getStaticConstructors(UnionTypeSpecGenerator.java:162)
    
  • Ran fern ir on the same schema and confirmed the IR correctly preserves "discriminantValue": "null".

  • Ran the current CasingConfiguration implementation and confirmed that computeName("null").camelSafe returns "null" rather than "null_".

  • Confirmed the same behavior for "true" and "false".

  • Added null, true, and false to the fallback set; the identifiers then resolve to null_, true_, and false_, and the full generator-utils test suite passes (186 tests).

Suggested fix

 private static final Set<String> JAVA_RESERVED_KEYWORDS = Set.of(
     ...
+    "false",
     ...
+    "null",
     ...
+    "true",
     ...
 );

A regression test should cover compressed or reconstructed names for all three values and verify that the Java identifier receives the _ suffix while the wire discriminator remains unchanged.

The downstream report that led to this investigation was langfuse-java PR #46. The minimal schema above is an independently verified end-to-end reproduction using fernapi/fern-java-sdk:4.19.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions