Skip to content

[java][spring][kotlin-spring] Make @JsonInclude/@JsonSetter opt-in to stop overriding global ObjectMapper - #24428

Open
Picazsoo wants to merge 29 commits into
OpenAPITools:masterfrom
Picazsoo:feature/fix-json-include
Open

[java][spring][kotlin-spring] Make @JsonInclude/@JsonSetter opt-in to stop overriding global ObjectMapper#24428
Picazsoo wants to merge 29 commits into
OpenAPITools:masterfrom
Picazsoo:feature/fix-json-include

Conversation

@Picazsoo

@Picazsoo Picazsoo commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #24401 and fixes #24365

Since 7.24.0 (#23993), the spring and kotlin-spring generators emit field-level Jackson annotations on model properties: @JsonInclude(NON_NULL) on optional non-nullable fields, @JsonSetter(nulls = ...) on those fields, and @JsonInclude(NON_ABSENT) on JsonNullable<T> fields.

Field-level @JsonInclude overrides the project-wide ObjectMapper inclusion policy (e.g. spring.jackson.default-property-inclusion=non_empty). Projects upgrading to 7.24.0 saw a silent wire-contract change: previously-omitted empty values (empty strings, lists, maps) started serializing, because per-field NON_NULL is looser than a stricter global setting and wins.

Separately, the @JsonSetter(nulls = ...) mode for optional non-nullable fields was hard-wired to openApiNullable: openApiNullable=true forced Nulls.FAIL (kotlin) / no annotation (spring), so the reasonable combination "keep openApiNullable=true for genuinely nullable optionals, but tolerate an incoming explicit null on non-nullable fields via Nulls.SKIP" was unreachable.

This PR makes annotation emission opt-in and configurable, restores 7.23.0-equivalent output by default, returns inclusion policy to the global ObjectMapper unless the user opts in, and decouples the @JsonSetter(nulls = ...) mode from openApiNullable.

Reasoning

  • The global ObjectMapper should stay the source of truth unless a user deliberately opts into generated annotations.
  • Upgrades should not silently change the serialized wire format, so the default reproduces pre-7.24.0 behavior.
  • A one-time warning is logged when the new options are unset (visible even on transitive upgrades); setting the option explicitly (even to false) silences it.
  • For deserialization, openApiNullable (a tri-state serialization concern) should not dictate null-tolerance on non-nullable fields. For a non-nullable field there is no absent-vs-null ambiguity, so tolerating a received null (Nulls.SKIP, preserving the Kotlin non-null default) is a legitimate, safe policy that should be reachable regardless of openApiNullable.
  • Users who want spec-honest annotations can still get them, with per-property control.

What changed

New options (both java spring and kotlin-spring)

  • generateJsonIncludeAnnotations (boolean, opt-in): true emits policy @JsonInclude (required-field protection + optional non-nullable policy). Unset emits none + warning; false emits none, warning silenced.
  • generateJsonSetterNullsAnnotations (boolean, opt-in): true emits @JsonSetter(nulls = ...) on optional non-nullable properties. Unset emits none + warning; false emits none, warning silenced.
  • optionalNonNullPropertyJsonInclude (enum, default NON_NULL): policy for optional non-nullable properties when generateJsonIncludeAnnotations=true. One of NON_NULL / NON_EMPTY / NON_DEFAULT / NONE (NONE emits nothing).
  • optionalNonNullPropertyJsonSetterNulls (enum SKIP / FAIL, unset by default): explicitly controls the @JsonSetter(nulls = ...) mode for optional non-nullable properties when generateJsonSetterNullsAnnotations=true, decoupling it from openApiNullable. When unset, the mode is derived from openApiNullable exactly as in 7.24.x (trueFAIL where supported, falseSKIP), so this is fully backward compatible. Setting it enables the previously-unreachable combinations, e.g. openApiNullable=true with SKIP. On the java spring generator this is also the only way to emit Nulls.FAIL.

Per-property overrides

  • New x-jackson-json-include-policy vendor extension (FIELD level) that always wins over the automatic matrix and the config options, even when generateJsonIncludeAnnotations=false. Accepts any valid Jackson Include value, or NONE (trimmed, case-insensitive) to emit nothing.
  • New x-jackson-json-setter-nulls vendor extension (FIELD level, SKIP / FAIL / NONE, trimmed/case-insensitive) that always wins over both the optionalNonNullPropertyJsonSetterNulls option and the openApiNullable-derived default, and is honored even when generateJsonSetterNullsAnnotations is unset. NONE emits nothing. It applies to required properties too (not just optional non-nullable ones).

Invalid values on either extension fail fast with an actionable error. Imports are added only when an annotation is emitted.

Automatic @JsonInclude matrix (when generateJsonIncludeAnnotations=true)

  • required + non-nullable: NON_NULL (spring) / ALWAYS (kotlin-spring)
  • required + nullable: ALWAYS
  • optional + non-nullable: optionalNonNullPropertyJsonInclude (default NON_NULL, NONE = omit)
  • optional + nullable: no annotation (the JsonNullable module governs inclusion)

Automatic @JsonSetter(nulls = ...) default (when generateJsonSetterNullsAnnotations=true, option unset)

Applies to optional non-nullable properties (backward-compatible with 7.24.x):

  • kotlin-spring: openApiNullable=trueNulls.FAIL, openApiNullable=falseNulls.SKIP.
  • spring: openApiNullable=true → no annotation, openApiNullable=falseNulls.SKIP.

Setting optionalNonNullPropertyJsonSetterNulls (or the per-property extension) overrides this default. A warning is logged when the resolved default is the risky "no annotation" case so the choice is visible.

Note: @JsonSetter(nulls = Nulls.SKIP) and emitting no @JsonSetter are not equivalent. With no annotation Jackson falls back to its global default (Nulls.SET), so an explicit JSON null overwrites the field's default value; Nulls.SKIP ignores the incoming null and preserves the default. SKIP is therefore strictly safer for non-nullable fields.

Notable fixes

  • @JsonInclude(NON_ABSENT) is no longer emitted on JsonNullable<T> fields (redundant; JsonNullable already governs inclusion).
  • @JsonSetter(nulls = ...) now sits on the field, not just the setter, so it is honored when Lombok (@Setter) generates the setter and no explicit setter method is emitted.

Refactors

  • New JsonIncludePolicy enum (mirrors Jackson Include plus a NONE sentinel) and JsonSetterNullsMode enum (NONE / SKIP / FAIL).
  • New TriStateBoolean to distinguish "unset" from explicit true/false (drives default-with-warning behavior).
  • New JsonAnnotationPolicyUtils centralizing parsing/validation/normalization and the layered resolution for both @JsonInclude and @JsonSetter(nulls), shared by both generators.
  • New CodegenConstants entries and VendorExtension.X_JACKSON_JSON_INCLUDE_POLICY / VendorExtension.X_JACKSON_JSON_SETTER_NULLS; templates (JavaSpring/pojo.mustache, kotlin-spring/dataClassOptVar.mustache, kotlin-spring/dataClassReqVar.mustache) render from the single resolved vendor extensions (including new Nulls.FAIL branches in pojo.mustache and dataClassReqVar.mustache).

Docs, tests, samples

  • Migration guide entry (7.24.x to 7.25.0) covering the change, all new options and vendor extensions, the SKIP-vs-Nulls.SET safety distinction, and how to restore strict Nulls.FAIL (PATCH) behavior.
  • Generator docs updated (spring.md, kotlin-spring.md, java-camel.md) with the new option and vendor-extension rows.
  • New tests for both generators: default matrix, each policy value, NONE, false, manual overrides (on JsonNullable, padded NONE, invalid values), per-schema import isolation, the Lombok-setter regression, and @JsonSetter(nulls) coverage (openApiNullable=true + SKIP (the previously-impossible combination), openApiNullable=true + FAIL, openApiNullable=false + FAIL, unset backward-compat defaults, per-property extension overrides, and invalid-value fail-fast). New fixtures under src/test/resources/3_0/spring/ and src/test/resources/3_0/kotlin/.
  • Regenerated samples (empty @JsonInclude(NON_NULL) annotations and unused imports removed).

Migration

  • No config change: output matches 7.23.0 (no field-level @JsonInclude / @JsonSetter); a warning is logged.
  • Set generateJsonIncludeAnnotations=false / generateJsonSetterNullsAnnotations=false to keep this behavior and silence the warnings.
  • Set generateJsonIncludeAnnotations=true (optionally with optionalNonNullPropertyJsonInclude) for spec-honest serialization annotations.
  • Set generateJsonSetterNullsAnnotations=true to emit @JsonSetter(nulls = ...); by default this restores strict Nulls.FAIL deserialization for optional non-nullable fields with openApiNullable=true (kotlin).
  • Set optionalNonNullPropertyJsonSetterNulls=SKIP (or FAIL) to control the mode independently of openApiNullable, e.g. keep openApiNullable=true while tolerating incoming nulls on non-nullable fields via SKIP.
  • Use x-jackson-json-include-policy or x-jackson-json-setter-nulls on a property for per-field control.

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
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Make @JsonInclude and @JsonSetter(nulls=...) opt‑in and configurable for spring and kotlin-spring. Default output now matches 7.23.0, so generated models no longer override the global ObjectMapper.

  • New Features

    • Add generateJsonIncludeAnnotations and generateJsonSetterNullsAnnotations (opt‑in; unset logs a one‑time warning).
    • Add optionalNonNullPropertyJsonInclude (NON_NULL/NON_EMPTY/NON_DEFAULT/NONE) and per‑property x-jackson-json-include-policy (NONE omits; extension wins). Do not emit @JsonInclude(NON_ABSENT) for JsonNullable<T>.
    • Add optionalNonNullPropertyJsonSetterNulls (SKIP/FAIL) decoupled from openApiNullable, plus per‑property x-jackson-json-setter-nulls (SKIP/FAIL/NONE; extension wins). Place @JsonSetter(nulls = Nulls.SKIP) on fields so Lombok setters honor it.
    • Update bin/configs across spring, spring-cloud, and kotlin-spring to opt in; some set optionalNonNullPropertyJsonInclude to NONE, NON_EMPTY, NON_DEFAULT, or NON_NULL. Regenerated samples and docs.
  • Refactors

    • Centralize parsing/validation in JsonAnnotationPolicyUtils (replaces JsonIncludePolicyUtils); introduce JsonIncludePolicy, JsonSetterNullsMode, and TriStateBoolean. Use CodegenConstants/VendorExtension keys. Fix NPE when optionalNonNullPropertyJsonSetterNulls is null and add a test guard; clarify migration note.

Written for commit 494f3d0. Summary will update on new commits.

Review in cubic

@Picazsoo
Picazsoo marked this pull request as ready for review July 24, 2026 13:36
@Picazsoo
Picazsoo marked this pull request as draft July 24, 2026 13:36

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

2 issues found across 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache:66">
P1: A manual `x-jackson-json-include-policy: NONE` now generates an annotation even though `NONE` means no annotation; because `SpringCodegen` also skips the import for this value, generated Java fails to compile. Suppress the template section for `NONE` (or remove that extension before rendering).</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache:66">
P2: Invalid `x-jackson-json-include-policy` values now produce uncompilable Java/Kotlin output because the template inserts the extension directly as a `JsonInclude.Include` constant. Validating the override against the supported policies (or rejecting it during generator processing) would turn this into an actionable generation error instead of a downstream compile failure.</violation>
</file>

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

Re-trigger cubic

@JsonInclude(JsonInclude.Include.NON_ABSENT)
{{/vendorExtensions.x-is-jackson-optional-nullable}}
{{#vendorExtensions.x-jackson-json-include-policy}}
@JsonInclude(JsonInclude.Include.{{{vendorExtensions.x-jackson-json-include-policy}}})

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.

P1: A manual x-jackson-json-include-policy: NONE now generates an annotation even though NONE means no annotation; because SpringCodegen also skips the import for this value, generated Java fails to compile. Suppress the template section for NONE (or remove that extension before rendering).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache, line 66:

<comment>A manual `x-jackson-json-include-policy: NONE` now generates an annotation even though `NONE` means no annotation; because `SpringCodegen` also skips the import for this value, generated Java fails to compile. Suppress the template section for `NONE` (or remove that extension before rendering).</comment>

<file context>
@@ -62,14 +62,9 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
-  @JsonInclude(JsonInclude.Include.NON_ABSENT)
-  {{/vendorExtensions.x-is-jackson-optional-nullable}}
+  {{#vendorExtensions.x-jackson-json-include-policy}}
+  @JsonInclude(JsonInclude.Include.{{{vendorExtensions.x-jackson-json-include-policy}}})
+  {{/vendorExtensions.x-jackson-json-include-policy}}
   {{/jackson}}
</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 was taken care of

@JsonInclude(JsonInclude.Include.NON_ABSENT)
{{/vendorExtensions.x-is-jackson-optional-nullable}}
{{#vendorExtensions.x-jackson-json-include-policy}}
@JsonInclude(JsonInclude.Include.{{{vendorExtensions.x-jackson-json-include-policy}}})

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: Invalid x-jackson-json-include-policy values now produce uncompilable Java/Kotlin output because the template inserts the extension directly as a JsonInclude.Include constant. Validating the override against the supported policies (or rejecting it during generator processing) would turn this into an actionable generation error instead of a downstream compile failure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache, line 66:

<comment>Invalid `x-jackson-json-include-policy` values now produce uncompilable Java/Kotlin output because the template inserts the extension directly as a `JsonInclude.Include` constant. Validating the override against the supported policies (or rejecting it during generator processing) would turn this into an actionable generation error instead of a downstream compile failure.</comment>

<file context>
@@ -62,14 +62,9 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
-  @JsonInclude(JsonInclude.Include.NON_ABSENT)
-  {{/vendorExtensions.x-is-jackson-optional-nullable}}
+  {{#vendorExtensions.x-jackson-json-include-policy}}
+  @JsonInclude(JsonInclude.Include.{{{vendorExtensions.x-jackson-json-include-policy}}})
+  {{/vendorExtensions.x-jackson-json-include-policy}}
   {{/jackson}}
</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 was taken care of

@Picazsoo
Picazsoo marked this pull request as ready for review July 24, 2026 14:00
@Picazsoo
Picazsoo marked this pull request as draft July 24, 2026 14:01

@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 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java:6910">
P2: The new invalid-override regression test cannot run because its referenced fixture `issue_24401_json_include_invalid_override.yaml` is not present in the repository. Adding the fixture (with `NOT_A_REAL_POLICY`) or removing the test reference is needed; otherwise the Kotlin Spring test suite fails unconditionally.</violation>
</file>

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

Re-trigger cubic

@Test(description = "Issue #24401 – invalid manual override fails fast (kotlin-spring)")
public void jsonInclude_manualOverride_invalid_failsWithActionableError() {
Throwable thrown = Assert.expectThrows(Throwable.class, () -> generateFromContract(
"src/test/resources/3_0/spring/issue_24401_json_include_invalid_override.yaml",

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: The new invalid-override regression test cannot run because its referenced fixture issue_24401_json_include_invalid_override.yaml is not present in the repository. Adding the fixture (with NOT_A_REAL_POLICY) or removing the test reference is needed; otherwise the Kotlin Spring test suite fails unconditionally.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java, line 6910:

<comment>The new invalid-override regression test cannot run because its referenced fixture `issue_24401_json_include_invalid_override.yaml` is not present in the repository. Adding the fixture (with `NOT_A_REAL_POLICY`) or removing the test reference is needed; otherwise the Kotlin Spring test suite fails unconditionally.</comment>

<file context>
@@ -6690,28 +6695,226 @@ public void requiredNullable_scenario3_optionalNonNullable_withJackson3() throws
+    @Test(description = "Issue #24401 – invalid manual override fails fast (kotlin-spring)")
+    public void jsonInclude_manualOverride_invalid_failsWithActionableError() {
+        Throwable thrown = Assert.expectThrows(Throwable.class, () -> generateFromContract(
+                "src/test/resources/3_0/spring/issue_24401_json_include_invalid_override.yaml",
+                Map.of(KotlinSpringServerCodegen.GENERATE_JSON_INCLUDE_ANNOTATIONS, "true")));
+
</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 was taken care of

@Picazsoo
Picazsoo marked this pull request as ready for review July 24, 2026 15:02
@Picazsoo
Picazsoo marked this pull request as draft July 24, 2026 15:02
@Picazsoo
Picazsoo marked this pull request as ready for review July 24, 2026 15:17

@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 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java:1277">
P2: Models using Lombok-generated setters ignore `generateJsonSetterNullsAnnotations=true`, so explicit JSON nulls can still overwrite optional non-nullable defaults. Render the `@JsonSetter` annotation on the field or otherwise support the Lombok setter path.</violation>
</file>

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

Re-trigger cubic

// setter so an explicit null in the payload does not overwrite the field's default. Only emitted when
// generateJsonSetterNullsAnnotations is explicitly enabled; otherwise deserialization defers to the mapper.
if (Boolean.TRUE.equals(generateJsonSetterNullsAnnotations) && !property.required && !property.isNullable && !openApiNullable) {
property.vendorExtensions.put("x-has-json-setter-nulls-skip", true);

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: Models using Lombok-generated setters ignore generateJsonSetterNullsAnnotations=true, so explicit JSON nulls can still overwrite optional non-nullable defaults. Render the @JsonSetter annotation on the field or otherwise support the Lombok setter path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java, line 1277:

<comment>Models using Lombok-generated setters ignore `generateJsonSetterNullsAnnotations=true`, so explicit JSON nulls can still overwrite optional non-nullable defaults. Render the `@JsonSetter` annotation on the field or otherwise support the Lombok setter path.</comment>

<file context>
@@ -1213,24 +1270,48 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
+        // setter so an explicit null in the payload does not overwrite the field's default. Only emitted when
+        // generateJsonSetterNullsAnnotations is explicitly enabled; otherwise deserialization defers to the mapper.
+        if (Boolean.TRUE.equals(generateJsonSetterNullsAnnotations) && !property.required && !property.isNullable && !openApiNullable) {
+            property.vendorExtensions.put("x-has-json-setter-nulls-skip", true);
+            model.imports.add("JsonSetter");
+            model.imports.add("Nulls");
</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.

@cubic-dev-ai, I think I took care of this issue. Please re-review

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.

@Picazsoo I have started the AI code review. It will take a few minutes to complete.

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

No issues found across 14 files

Re-trigger cubic

@Setter private boolean suspendFunctions = false;
@Getter @Setter private String optionalNonNullPropertyJsonInclude = "NON_NULL";
// Tri-state: null = unset (weak default + warning), Boolean.FALSE = weak (muted), Boolean.TRUE = strict emission.
@Getter @Setter private Boolean generateJsonIncludeAnnotations = null;

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.

I would suggest creating a concrete enum here to represent the tri-state.

Having a boxed boolean where the null has a separate values is always very difficulty to interpret, and given that it is not unusual to have it where only true and false are actually expected means that it is not unusual for it to be hard to interpret for no reason at all.

So rather than having Boolean and a comment next to the variables I think you get enormous benefit from just creating a small explicit enum that can express everything itself and also prevent reoccurring unclarities.

@Picazsoo Picazsoo Jul 28, 2026

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.

Thanks for the comment! I will do so. I started with a primitive boolean and a separate boolean "unset" flag. Then I changed over to the tri-state Boolean. I agree that the implicit tri-state is a bit hacky.

@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 1721 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="samples/client/petstore/spring-http-interface-reactive-noResponseEntity/src/main/java/org/openapitools/model/ContainerDefaultValue.java">

<violation number="1" location="samples/client/petstore/spring-http-interface-reactive-noResponseEntity/src/main/java/org/openapitools/model/ContainerDefaultValue.java:29">
P2: Removing @JsonInclude(JsonInclude.Include.NON_ABSENT) from JsonNullable fields changes serialization behavior: undefined nullableArray and nullableArrayWithDefault will now be included in JSON output instead of omitted. Consumers relying on these optional fields being absent when unset will receive unexpected values, breaking the API contract.</violation>
</file>

Note: This PR contains a large number of files. cubic only reviews up to 200 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.

Re-trigger cubic

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", comments = "Generator version: 7.25.0-SNAPSHOT")
public class ContainerDefaultValue {

@JsonInclude(JsonInclude.Include.NON_ABSENT)

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: Removing @JsonInclude(JsonInclude.Include.NON_ABSENT) from JsonNullable fields changes serialization behavior: undefined nullableArray and nullableArrayWithDefault will now be included in JSON output instead of omitted. Consumers relying on these optional fields being absent when unset will receive unexpected values, breaking the API contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/spring-http-interface-reactive-noResponseEntity/src/main/java/org/openapitools/model/ContainerDefaultValue.java, line 29:

<comment>Removing @JsonInclude(JsonInclude.Include.NON_ABSENT) from JsonNullable fields changes serialization behavior: undefined nullableArray and nullableArrayWithDefault will now be included in JSON output instead of omitted. Consumers relying on these optional fields being absent when unset will receive unexpected values, breaking the API contract.</comment>

<file context>
@@ -26,14 +25,12 @@
 
-  @JsonInclude(JsonInclude.Include.NON_ABSENT)
   private JsonNullable<List<String>> nullableArray = JsonNullable.<List<String>>undefined();
 
   private JsonNullable<List<String>> nullableRequiredArray = JsonNullable.<List<String>>undefined();
 
</file context>

@Picazsoo Picazsoo changed the title Feature/fix json include [java][spring][kotlin-spring] Make @JsonInclude/@JsonSetter opt-in to stop overriding global ObjectMapper (#24401) Jul 29, 2026
@Picazsoo

Copy link
Copy Markdown
Contributor Author

Marking as ready for review, but definitely do not merge yet

@Picazsoo
Picazsoo marked this pull request as ready for review July 30, 2026 13:15
@Picazsoo

Copy link
Copy Markdown
Contributor Author

Tagging @gs-covariance, @MelleD , @jorgerod in the pull request with proposed solution as promised.

@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 709 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="samples/client/petstore/spring-http-interface-springboot-4/src/main/java/org/openapitools/model/AdditionalPropertiesStringDto.java">

<violation number="1" location="samples/client/petstore/spring-http-interface-springboot-4/src/main/java/org/openapitools/model/AdditionalPropertiesStringDto.java:34">
P1: @JsonSetter(nulls = Nulls.SKIP) is emitted on `name` which is `@Nullable`, but per the PR's rules this annotation should only appear on optional non-nullable properties. Skip nullable properties to match the intended matrix.</violation>
</file>

Note: This PR contains a large number of files. cubic only reviews up to 200 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.

Re-trigger cubic

public class AdditionalPropertiesStringDto {

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonSetter(nulls = Nulls.SKIP)

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.

P1: @JsonSetter(nulls = Nulls.SKIP) is emitted on name which is @Nullable, but per the PR's rules this annotation should only appear on optional non-nullable properties. Skip nullable properties to match the intended matrix.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/spring-http-interface-springboot-4/src/main/java/org/openapitools/model/AdditionalPropertiesStringDto.java, line 34:

<comment>@JsonSetter(nulls = Nulls.SKIP) is emitted on `name` which is `@Nullable`, but per the PR's rules this annotation should only appear on optional non-nullable properties. Skip nullable properties to match the intended matrix.</comment>

<file context>
@@ -31,6 +31,7 @@
 public class AdditionalPropertiesStringDto {
 
   @JsonInclude(JsonInclude.Include.NON_NULL)
+  @JsonSetter(nulls = Nulls.SKIP)
   private @Nullable String name;
 
</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.

false positive. name is actually optional non-nullable here:

    AdditionalPropertiesString:
      type: object
      properties:
        name:
          type: string

@Picazsoo

Picazsoo commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Hi @gs-covariance, @MelleD , @jorgerod. If possible, could you try to build this branch and try it in your project to make sure it really satisfies all requirements? I tried it in our project and it seems to work fine, but I am not really the target audience as the regression was not a problem for us.

@singlaHarish

singlaHarish commented Aug 4, 2026

Copy link
Copy Markdown

@Picazsoo Thank you for working on the fix. We have run into this problem and this week. Do you plan to merge and these changes and release it soon ?

@Picazsoo

Picazsoo commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@Picazsoo Thank you for working on the fix. We have run into this problem and this week. Do you plan to merge and these changes and release it soon ?

Hello @singlaHarish , I am not a maintainer of this project. Just a contributor. If all goes well, I expect this to be hopefully merged by the time of the next typical release. In the meantime it would be greatly appreciated if you could try to build this branch and try it in you project whether it resolves the issues for you.

I am pretty sure I pasted a possible workaround for this issue - you just need to modify and sideload a single mustache template file and should be good until the version with a proper fix.

Once again sorry for any inconvenience caused.

@singlaHarish

singlaHarish commented Aug 4, 2026

Copy link
Copy Markdown

@Picazsoo Thank you for working on the fix. We have run into this problem and this week. Do you plan to merge and these changes and release it soon ?

Hello @singlaHarish , I am not a maintainer of this project. Just a contributor. If all goes well, I expect this to be hopefully merged by the time of the next typical release. In the meantime it would be greatly appreciated if you could try to build this branch and try it in you project whether it resolves the issues for you.

I am pretty sure I pasted a possible workaround for this issue - you just need to modify and sideload a single mustache template file and should be good until the version with a proper fix.

Once again sorry for any inconvenience caused.

Thank you @Picazsoo!

I have tested after checking in your feature and works fine for me! The JsonInclude NonNull annotation is no longer in the generated code

<generateJsonIncludeAnnotations>false</generateJsonIncludeAnnotations>

`Generated(value = "org.openapitools.codegen.languages.SpringCodegen", comments = "Generator version: 7.25.0-SNAPSHOT")
public class A {

private @nullable String x;
`

@glandais-nickel

Copy link
Copy Markdown

Hi @gs-covariance, @MelleD , @jorgerod. If possible, could you try to build this branch and try it in your project to make sure it really satisfies all requirements? I tried it in our project and it seems to work fine, but I am not really the target audience as the regression was not a problem for us.

Works great on our side too, with or without generateJsonIncludeAnnotations / generateJsonSetterNullsAnnotations set to false

Thanks !

Picazsoo and others added 2 commits August 7, 2026 22:32
…nullable fields

Add optionalNonNullPropertyJsonSetterNulls option (SKIP/FAIL) and the per-property x-jackson-json-setter-nulls vendor extension (SKIP/FAIL/NONE) to the spring and kotlin-spring generators, mirroring the JsonInclude mechanism. When unset, behavior is byte-identical to today (openApiNullable-derived default), so this is fully backward compatible. Setting them enables previously-unreachable combinations such as openApiNullable=true with SKIP.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts:
#	samples/openapi3/server/petstore/springboot-4-jspecify/src/main/java/org/openapitools/model/Foo.java

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

All reported issues were addressed across 24 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread docs/migration-guide.adoc Outdated
Picazsoo and others added 2 commits August 7, 2026 22:48
…clarify migration note

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Picazsoo

Picazsoo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Just a heads up, I made one extra tweak beyond the main fix: I decoupled the @JsonSetter(nulls = ...) mode from openApiNullable. It was previously hard-wired (on kotlin-spring, trueFAIL, falseSKIP; on java-spring, only SKIP and only when false), which made a reasonable combo unreachable, i.e. keeping openApiNullable=true for genuinely nullable optionals while still tolerating an incoming null on non-nullable fields. The new optionalNonNullPropertyJsonSetterNulls option lets you set that explicitly, and when unset it falls back to the old openApiNullable-derived behavior, so it's fully backward compatible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

5 participants