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:
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:
instead of:
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.
Java SDK generation fails on discriminator values
null,true, andfalseWhen a discriminated union's discriminator value is the literal string
"null"—or equivalently"true"/"false"—the Java generator aborts with:Reproduction
The following minimal OpenAPI schema reproduces the failure against the current Java generator:
Running
fern irconfirms that the IR correctly preserves: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_KEYWORDSappends_when a generated Java identifier conflicts with a reserved token. This convention already works for ordinary Java keywords—for example, the existingSipHeaderActionfixture underseed/java-sdk/reserved-keywordsgeneratesstatic_(...)while retaining@JsonTypeName("static")for the wire value.The fallback set omits the three Java literals:
They are not classified as JLS keywords, but none of them is a valid Java identifier. As a result:
currently evaluates to:
instead of:
UnionGeneratorthen passesnullto JavaPoet as a method name, and JavaPoet correctly rejects it.KeyWordUtils.java, a separate reserved-token list in the same Java generator, already containsnull,true, andfalse. 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 --localagainst the minimal schema above using the publishedfernapi/fern-java-sdk:4.19.0Docker image. Generation aborts with:Ran
fern iron the same schema and confirmed the IR correctly preserves"discriminantValue": "null".Ran the current
CasingConfigurationimplementation and confirmed thatcomputeName("null").camelSafereturns"null"rather than"null_".Confirmed the same behavior for
"true"and"false".Added
null,true, andfalseto the fallback set; the identifiers then resolve tonull_,true_, andfalse_, and the fullgenerator-utilstest 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-javaPR #46. The minimal schema above is an independently verified end-to-end reproduction usingfernapi/fern-java-sdk:4.19.0.