From 6d10d117a879a545eac5a90f6a11747395ea8e3f Mon Sep 17 00:00:00 2001 From: ChoMinGi Date: Sat, 15 Aug 2026 16:31:54 +0900 Subject: [PATCH 1/2] fix(java): escape null/true/false discriminator values in generated identifiers CasingConfiguration.JAVA_RESERVED_KEYWORDS omitted the Java literals null, true, and false, so a discriminated union whose discriminator value is one of those strings produced an unescaped Java identifier and crashed JavaPoet with "not a valid name: null". KeyWordUtils already treats these as reserved; this brings CasingConfiguration's fallback set in line with it. Fixes #17429 --- .../fern/java/utils/CasingConfiguration.java | 3 +++ .../java/utils/CasingConfigurationTest.java | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/generators/java/generator-utils/src/main/java/com/fern/java/utils/CasingConfiguration.java b/generators/java/generator-utils/src/main/java/com/fern/java/utils/CasingConfiguration.java index d664f0aee384..ee3e3ff6c365 100644 --- a/generators/java/generator-utils/src/main/java/com/fern/java/utils/CasingConfiguration.java +++ b/generators/java/generator-utils/src/main/java/com/fern/java/utils/CasingConfiguration.java @@ -59,6 +59,7 @@ public final class CasingConfiguration { "else", "enum", "extends", + "false", "final", "finally", "float", @@ -73,6 +74,7 @@ public final class CasingConfiguration { "long", "native", "new", + "null", "package", "private", "protected", @@ -88,6 +90,7 @@ public final class CasingConfiguration { "throw", "throws", "transient", + "true", "try", "void", "volatile", diff --git a/generators/java/generator-utils/src/test/java/com/fern/java/utils/CasingConfigurationTest.java b/generators/java/generator-utils/src/test/java/com/fern/java/utils/CasingConfigurationTest.java index 4da8755bfb64..4414b0ed2ae3 100644 --- a/generators/java/generator-utils/src/test/java/com/fern/java/utils/CasingConfigurationTest.java +++ b/generators/java/generator-utils/src/test/java/com/fern/java/utils/CasingConfigurationTest.java @@ -957,4 +957,24 @@ private static CasingConfiguration buildConfig(boolean smartCasing, String langu root.set("casingsConfig", casingsConfig); return CasingConfiguration.fromIrJson(root); } + + // ===== keyword sanitization: Java literals ===== + + @Nested + class JavaLiteralKeywordTests { + + @ParameterizedTest + @CsvSource({"null,null_", "true,true_", "false,false_"}) + void computeName_escapesJavaLiterals(String input, String expectedSafeName) { + CasingConfiguration config = buildConfig(true, "java", null); + CasingConfiguration.NameParts parts = config.computeName(input); + assertThat(parts.camelSafe).isEqualTo(expectedSafeName); + } + + @Test + void computeName_ordinaryKeywordStillEscaped() { + CasingConfiguration config = buildConfig(true, "java", null); + assertThat(config.computeName("default").camelSafe).isEqualTo("default_"); + } + } } From 6cb52ae6e3c92c8e64460aa61cf22d645ead8b53 Mon Sep 17 00:00:00 2001 From: ChoMinGi Date: Sat, 15 Aug 2026 16:36:20 +0900 Subject: [PATCH 2/2] chore(java): add changelog entry and quote CsvSource literals - Add versions.yml changelog entry per REVIEW.md convention for generator changes - Quote null/true/false in @CsvSource so intent is explicit regardless of nullValues configuration (nitpickybot suggestion) --- .../com/fern/java/utils/CasingConfigurationTest.java | 2 +- .../unreleased/fix-null-true-false-discriminator.yml | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 generators/java/sdk/changes/unreleased/fix-null-true-false-discriminator.yml diff --git a/generators/java/generator-utils/src/test/java/com/fern/java/utils/CasingConfigurationTest.java b/generators/java/generator-utils/src/test/java/com/fern/java/utils/CasingConfigurationTest.java index 4414b0ed2ae3..7ff5aba627d9 100644 --- a/generators/java/generator-utils/src/test/java/com/fern/java/utils/CasingConfigurationTest.java +++ b/generators/java/generator-utils/src/test/java/com/fern/java/utils/CasingConfigurationTest.java @@ -964,7 +964,7 @@ private static CasingConfiguration buildConfig(boolean smartCasing, String langu class JavaLiteralKeywordTests { @ParameterizedTest - @CsvSource({"null,null_", "true,true_", "false,false_"}) + @CsvSource({"'null',null_", "'true',true_", "'false',false_"}) void computeName_escapesJavaLiterals(String input, String expectedSafeName) { CasingConfiguration config = buildConfig(true, "java", null); CasingConfiguration.NameParts parts = config.computeName(input); diff --git a/generators/java/sdk/changes/unreleased/fix-null-true-false-discriminator.yml b/generators/java/sdk/changes/unreleased/fix-null-true-false-discriminator.yml new file mode 100644 index 000000000000..54358ea2a22c --- /dev/null +++ b/generators/java/sdk/changes/unreleased/fix-null-true-false-discriminator.yml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../../../../../fern-changes-yml.schema.json + +- summary: | + Escape the Java literals `null`, `true`, and `false` when they're used as a + discriminated union's discriminator value. Previously the generated Java + identifier was left unescaped (e.g. a static factory method literally named + `null`), which JavaPoet rejected with `not a valid name: null`, aborting + generation. The identifier is now escaped to `null_`/`true_`/`false_` + (matching the existing convention for other reserved words like `static`), + while the serialized discriminator value is unchanged. + type: fix