[Spring] Add JsonProperty access=READ_ONLY for readOnly properties - #24929
antoineclech-arkea wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
3 issues found across 1 file
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:259">
P1: When Lombok generates the setter through `lombok.Setter` or `lombok.Data`, this line is not rendered and no Lombok-side annotation sets `READ_ONLY`, so read-only properties remain writable by Jackson. Apply the access setting to the Lombok-generated accessor or field path as well.</violation>
<violation number="2" location="modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache:259">
P1: When `openApiNullable` is enabled for a non-required nullable read-only property, this guard suppresses the new annotation, so Jackson still auto-detects the setter and accepts request values. Emit `READ_ONLY` for this case too while retaining any nullable-specific mapping.</violation>
<violation number="3" location="modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache:259">
P2: When the Spring generator runs with `withXml` enabled, this change silently removes `@JacksonXmlProperty` and `@JacksonXmlElementWrapper` from every generated setter. The original setter used the shared `{{>jackson_annotations}}` partial, which emits those XML annotations under `{{#withXml}}`; the new inline `@JsonProperty(...)` only emits the JSON annotation, so XML deserialization mapping (element name/wrapper) on setters is lost while the getter keeps them. Preserve the partial and add the `access = JsonProperty.Access.READ_ONLY` parameter to `jackson_annotations.mustache` instead of replacing the partial inline.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| @Deprecated | ||
| {{/deprecated}} | ||
| {{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{>jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) { | ||
| {{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}} @JsonProperty(value = "{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) { |
There was a problem hiding this comment.
P1: When Lombok generates the setter through lombok.Setter or lombok.Data, this line is not rendered and no Lombok-side annotation sets READ_ONLY, so read-only properties remain writable by Jackson. Apply the access setting to the Lombok-generated accessor or field path as well.
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 259:
<comment>When Lombok generates the setter through `lombok.Setter` or `lombok.Data`, this line is not rendered and no Lombok-side annotation sets `READ_ONLY`, so read-only properties remain writable by Jackson. Apply the access setting to the Lombok-generated accessor or field path as well.</comment>
<file context>
@@ -256,7 +256,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
@Deprecated
{{/deprecated}}
-{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{>jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) {
+{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}} @JsonProperty(value = "{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) {
this.{{name}} = {{name}};
}
</file context>
| @Deprecated | ||
| {{/deprecated}} | ||
| {{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{>jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) { | ||
| {{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}} @JsonProperty(value = "{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) { |
There was a problem hiding this comment.
P1: When openApiNullable is enabled for a non-required nullable read-only property, this guard suppresses the new annotation, so Jackson still auto-detects the setter and accepts request values. Emit READ_ONLY for this case too while retaining any nullable-specific mapping.
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 259:
<comment>When `openApiNullable` is enabled for a non-required nullable read-only property, this guard suppresses the new annotation, so Jackson still auto-detects the setter and accepts request values. Emit `READ_ONLY` for this case too while retaining any nullable-specific mapping.</comment>
<file context>
@@ -256,7 +256,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
@Deprecated
{{/deprecated}}
-{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{>jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) {
+{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}} @JsonProperty(value = "{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) {
this.{{name}} = {{name}};
}
</file context>
| @Deprecated | ||
| {{/deprecated}} | ||
| {{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{>jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) { | ||
| {{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}} @JsonProperty(value = "{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) { |
There was a problem hiding this comment.
P2: When the Spring generator runs with withXml enabled, this change silently removes @JacksonXmlProperty and @JacksonXmlElementWrapper from every generated setter. The original setter used the shared {{>jackson_annotations}} partial, which emits those XML annotations under {{#withXml}}; the new inline @JsonProperty(...) only emits the JSON annotation, so XML deserialization mapping (element name/wrapper) on setters is lost while the getter keeps them. Preserve the partial and add the access = JsonProperty.Access.READ_ONLY parameter to jackson_annotations.mustache instead of replacing the partial inline.
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 259:
<comment>When the Spring generator runs with `withXml` enabled, this change silently removes `@JacksonXmlProperty` and `@JacksonXmlElementWrapper` from every generated setter. The original setter used the shared `{{>jackson_annotations}}` partial, which emits those XML annotations under `{{#withXml}}`; the new inline `@JsonProperty(...)` only emits the JSON annotation, so XML deserialization mapping (element name/wrapper) on setters is lost while the getter keeps them. Preserve the partial and add the `access = JsonProperty.Access.READ_ONLY` parameter to `jackson_annotations.mustache` instead of replacing the partial inline.</comment>
<file context>
@@ -256,7 +256,7 @@ public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}
@Deprecated
{{/deprecated}}
-{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{>jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) {
+{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}} @JsonProperty(value = "{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullableAnnotation}}{{>nullableDataType}} {{name}}) {
this.{{name}} = {{name}};
}
</file context>
bdbded2 to
c7e18e0
Compare
|
@cubic review |
@antoineclech-arkea I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
c7e18e0 to
f9ecd99
Compare
|
@cubic review |
@antoineclech-arkea I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 existing issue remains and 1 new issue found across 3 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/jackson_annotations.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/JavaSpring/jackson_annotations.mustache:1">
P2: This template change alters generated output for every readOnly property in existing Spring samples, but no samples were regenerated. The committed samples (e.g. samples/client/petstore/spring-http-interface/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, which currently emits `@JsonProperty("bar")` for its readOnly fields) will now produce `@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)`, so the sample-verification CI will fail. Regenerate the affected Spring samples (./bin/generate-samples.sh for the spring configs) as part of this PR.</violation>
</file>
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Re-trigger cubic
| @@ -1,4 +1,4 @@ | |||
| @JsonProperty("{{baseName}}") | |||
| @JsonProperty({{#isReadOnly}}value = {{/isReadOnly}}"{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}}) | |||
There was a problem hiding this comment.
P2: This template change alters generated output for every readOnly property in existing Spring samples, but no samples were regenerated. The committed samples (e.g. samples/client/petstore/spring-http-interface/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, which currently emits @JsonProperty("bar") for its readOnly fields) will now produce @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY), so the sample-verification CI will fail. Regenerate the affected Spring samples (./bin/generate-samples.sh for the spring configs) as part of this PR.
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/jackson_annotations.mustache, line 1:
<comment>This template change alters generated output for every readOnly property in existing Spring samples, but no samples were regenerated. The committed samples (e.g. samples/client/petstore/spring-http-interface/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, which currently emits `@JsonProperty("bar")` for its readOnly fields) will now produce `@JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)`, so the sample-verification CI will fail. Regenerate the affected Spring samples (./bin/generate-samples.sh for the spring configs) as part of this PR.</comment>
<file context>
@@ -1,4 +1,4 @@
- @JsonProperty("{{baseName}}")
+ @JsonProperty({{#isReadOnly}}value = {{/isReadOnly}}"{{baseName}}"{{#isReadOnly}}, access = JsonProperty.Access.READ_ONLY{{/isReadOnly}})
{{#withXml}}
@JacksonXmlProperty(localName = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#isXmlAttribute}}, isAttribute = true{{/isXmlAttribute}}{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}})
</file context>
f9ecd99 to
546d04d
Compare
|
@cubic review |
@antoineclech-arkea I have started the AI code review. It will take a few minutes to complete. |
Fixes OpenAPITools#20612 Fixes generation of read-only properties in Spring models to include @JsonProperty(access = JsonProperty.Access.READ_ONLY) on setters. This prevents Jackson from accepting read-only properties during deserialization, enforcing the OpenAPI contract. Changes: - Modified jackson_annotations.mustache to emit access=READ_ONLY for readOnly properties - Added unit test with readOnly property test spec - Regenerated affected Spring samples
66b33e4 to
f8f08c1
Compare
|
@cubic review |
@antoineclech-arkea I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
2 issues found across 15 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-noResponseEntity/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java">
<violation number="1" location="samples/client/petstore/spring-http-interface-noResponseEntity/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java:42">
P2: Jackson `READ_ONLY` means serialize but never deserialize. That is the right contract for a server model (don't accept readOnly values in requests), but this shared template is also used by client-side Spring generators — this very sample is `samples/client/petstore/spring-http-interface-noResponseEntity`. For a client, a readOnly value arrives in the server response and must be deserialized; with `READ_ONLY` on the getter/setter Jackson now silently skips it, so `getBar()`/`getFoo()` return null for values the server actually sent. This is a real behavior change for Spring clients that the PR does not account for (it only frames the server benefit). Confirm client deserialization of readOnly values is not needed, or gate the `READ_ONLY` emission to server-side generation.</violation>
</file>
<file name="samples/client/petstore/spring-http-interface/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java">
<violation number="1" location="samples/client/petstore/spring-http-interface/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java:42">
P2: For these Spring CLIENT models the shared READ_ONLY setter change prevents Jackson from deserializing readOnly fields, so the client silently receives null for server-generated values (e.g. IDs) in API responses. OpenAPI readOnly means 'may be sent in responses but should not be sent in requests', so a client must still be able to read these fields. Gate the READ_ONLY emission to server-side generators only, or apply it only where the model is used for request deserialization.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| */ | ||
|
|
||
| @JsonProperty("bar") | ||
| @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY) |
There was a problem hiding this comment.
P2: Jackson READ_ONLY means serialize but never deserialize. That is the right contract for a server model (don't accept readOnly values in requests), but this shared template is also used by client-side Spring generators — this very sample is samples/client/petstore/spring-http-interface-noResponseEntity. For a client, a readOnly value arrives in the server response and must be deserialized; with READ_ONLY on the getter/setter Jackson now silently skips it, so getBar()/getFoo() return null for values the server actually sent. This is a real behavior change for Spring clients that the PR does not account for (it only frames the server benefit). Confirm client deserialization of readOnly values is not needed, or gate the READ_ONLY emission to server-side generation.
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-noResponseEntity/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, line 42:
<comment>Jackson `READ_ONLY` means serialize but never deserialize. That is the right contract for a server model (don't accept readOnly values in requests), but this shared template is also used by client-side Spring generators — this very sample is `samples/client/petstore/spring-http-interface-noResponseEntity`. For a client, a readOnly value arrives in the server response and must be deserialized; with `READ_ONLY` on the getter/setter Jackson now silently skips it, so `getBar()`/`getFoo()` return null for values the server actually sent. This is a real behavior change for Spring clients that the PR does not account for (it only frames the server benefit). Confirm client deserialization of readOnly values is not needed, or gate the `READ_ONLY` emission to server-side generation.</comment>
<file context>
@@ -39,12 +39,12 @@ public HasOnlyReadOnlyDto bar(@Nullable String bar) {
*/
- @JsonProperty("bar")
+ @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
</file context>
| */ | ||
|
|
||
| @JsonProperty("bar") | ||
| @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY) |
There was a problem hiding this comment.
P2: For these Spring CLIENT models the shared READ_ONLY setter change prevents Jackson from deserializing readOnly fields, so the client silently receives null for server-generated values (e.g. IDs) in API responses. OpenAPI readOnly means 'may be sent in responses but should not be sent in requests', so a client must still be able to read these fields. Gate the READ_ONLY emission to server-side generators only, or apply it only where the model is used for request deserialization.
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/src/main/java/org/openapitools/model/HasOnlyReadOnlyDto.java, line 42:
<comment>For these Spring CLIENT models the shared READ_ONLY setter change prevents Jackson from deserializing readOnly fields, so the client silently receives null for server-generated values (e.g. IDs) in API responses. OpenAPI readOnly means 'may be sent in responses but should not be sent in requests', so a client must still be able to read these fields. Gate the READ_ONLY emission to server-side generators only, or apply it only where the model is used for request deserialization.</comment>
<file context>
@@ -39,12 +39,12 @@ public HasOnlyReadOnlyDto bar(@Nullable String bar) {
*/
- @JsonProperty("bar")
+ @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
</file context>
There was a problem hiding this comment.
6 issues found across 78 files (changes from recent commits).
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/server/petstore/springboot-reactive/src/main/java/org/openapitools/model/HasOnlyReadOnly.java">
<violation number="1" location="samples/server/petstore/springboot-reactive/src/main/java/org/openapitools/model/HasOnlyReadOnly.java:45">
P2: This is a silent runtime behavior change for every existing Spring-generated API, not the "fully backward compatible" change the PR claims. Once @JsonProperty(access = READ_ONLY) is on a setter, Jackson stops calling it during deserialization, so any client that currently sends readOnly fields (e.g. an id reference) will have those values silently dropped rather than stored — the JSON is ignored, not rejected with an error. Existing generated clients/servers built on the previous behavior will diverge without any exception being thrown. Confirm this compatibility impact is intended and update the PR's "no breaking changes" claim, or restrict the enforcement to new opt-in generators.</violation>
</file>
<file name="samples/server/petstore/springboot-x-implements-skip/src/main/java/org/openapitools/model/ReadOnlyFirstDto.java">
<violation number="1" location="samples/server/petstore/springboot-x-implements-skip/src/main/java/org/openapitools/model/ReadOnlyFirstDto.java:45">
P2: The READ_ONLY annotations are only emitted in the non-Lombok getter/setter blocks of JavaSpring/pojo.mustache (lines 242 and 259). SpringCodegen exposes a real lombokAnnotations option (Getter/Setter/Data); for those configs the getter/setter (and thus any @JsonProperty) are not generated, so readOnly properties carry no READ_ONLY access and Jackson still accepts them during deserialization. This contradicts the PR's claim that the change prevents Jackson from accepting read-only properties in Spring models, and means the fix is silently incomplete for Lombok-based Spring generation.</violation>
</file>
<file name="samples/server/petstore/springboot-include-http-request-context/src/main/java/org/openapitools/model/NameDto.java">
<violation number="1" location="samples/server/petstore/springboot-include-http-request-context/src/main/java/org/openapitools/model/NameDto.java:84">
P3: The `access = JsonProperty.Access.READ_ONLY` is emitted on both the getter and the setter. Jackson merges `@JsonProperty` access across all accessors of a logical property, so a single accessor annotation (getter or setter) is sufficient to make the property read-only; the duplicate on the second accessor is redundant and only adds noise to the generated public API. Emit it on one accessor per readOnly property.</violation>
</file>
<file name="samples/client/petstore/spring-http-interface-useHttpServiceProxyFactoryInterfacesConfigurator/src/main/java/org/openapitools/model/Name.java">
<violation number="1" location="samples/client/petstore/spring-http-interface-useHttpServiceProxyFactoryInterfacesConfigurator/src/main/java/org/openapitools/model/Name.java:79">
P2: `JsonProperty.Access.READ_ONLY` on the setter makes Jackson skip the property entirely during deserialization (READ_ONLY disables the setter, not just the fluent method). Because the Spring generator emits one shared model used for both request and response DTOs, this also stops readOnly fields from being populated whenever that model is deserialized on the client side — e.g. a Spring WebClient or a shared DTO parsing a server response. Server-generated readOnly fields such as `id` or `createdAt` will come back null after this change, which the PR's "no breaking changes" claim does not cover. Note the `Java` generator deliberately avoids this by making readOnly setters `private` with an empty-`JsonNullable` path rather than annotating READ_ONLY. Confirm this template is only used where the model is never deserialized from responses; otherwise the READ_ONLY scope needs to be applied so readOnly response fields are still readable.</violation>
</file>
<file name="samples/server/petstore/springboot-builtin-validation/src/main/java/org/openapitools/model/HasOnlyReadOnly.java">
<violation number="1" location="samples/server/petstore/springboot-builtin-validation/src/main/java/org/openapitools/model/HasOnlyReadOnly.java:45">
P3: The chain/fluent setters `bar(String)` and `foo(String)` are emitted with no Jackson annotation at all, while `access = READ_ONLY` is applied only to the getter and void setter. Whether a readOnly value is refused during request deserialization therefore depends entirely on Jackson merging the fluent setters into the same logical read-only property. Make the guarantee explicit by emitting `@JsonProperty(value = "...", access = JsonProperty.Access.READ_ONLY)` on the chain setters for readOnly vars in JavaSpring/pojo.mustache, so the enforcement is self-documenting and robust.</violation>
</file>
<file name="samples/server/petstore/springboot-spring-pageable-delegatePattern/src/main/java/org/openapitools/model/ReadOnlyFirst.java">
<violation number="1" location="samples/server/petstore/springboot-spring-pageable-delegatePattern/src/main/java/org/openapitools/model/ReadOnlyFirst.java:43">
P3: This fix adds READ_ONLY access for readOnly properties, but the Spring generator does not handle the symmetric writeOnly case. The existing JavaJaxRS generators already emit access = JsonProperty.Access.WRITE_ONLY for writeOnly properties, while Spring emits nothing, so writeOnly properties (e.g. passwords, secrets) remain fully serialized into responses. Extend the same conditional handling to writeOnly to match the established JaxRS pattern and avoid leaking write-only data.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
|
||
| @Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED) | ||
| @JsonProperty("bar") | ||
| @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY) |
There was a problem hiding this comment.
P2: This is a silent runtime behavior change for every existing Spring-generated API, not the "fully backward compatible" change the PR claims. Once @JsonProperty(access = READ_ONLY) is on a setter, Jackson stops calling it during deserialization, so any client that currently sends readOnly fields (e.g. an id reference) will have those values silently dropped rather than stored — the JSON is ignored, not rejected with an error. Existing generated clients/servers built on the previous behavior will diverge without any exception being thrown. Confirm this compatibility impact is intended and update the PR's "no breaking changes" claim, or restrict the enforcement to new opt-in generators.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/server/petstore/springboot-reactive/src/main/java/org/openapitools/model/HasOnlyReadOnly.java, line 45:
<comment>This is a silent runtime behavior change for every existing Spring-generated API, not the "fully backward compatible" change the PR claims. Once @JsonProperty(access = READ_ONLY) is on a setter, Jackson stops calling it during deserialization, so any client that currently sends readOnly fields (e.g. an id reference) will have those values silently dropped rather than stored — the JSON is ignored, not rejected with an error. Existing generated clients/servers built on the previous behavior will diverge without any exception being thrown. Confirm this compatibility impact is intended and update the PR's "no breaking changes" claim, or restrict the enforcement to new opt-in generators.</comment>
<file context>
@@ -42,12 +42,12 @@ public HasOnlyReadOnly bar(@Nullable String bar) {
@Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- @JsonProperty("bar")
+ @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
</file context>
|
|
||
| @Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED) | ||
| @JsonProperty("bar") | ||
| @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY) |
There was a problem hiding this comment.
P2: The READ_ONLY annotations are only emitted in the non-Lombok getter/setter blocks of JavaSpring/pojo.mustache (lines 242 and 259). SpringCodegen exposes a real lombokAnnotations option (Getter/Setter/Data); for those configs the getter/setter (and thus any @JsonProperty) are not generated, so readOnly properties carry no READ_ONLY access and Jackson still accepts them during deserialization. This contradicts the PR's claim that the change prevents Jackson from accepting read-only properties in Spring models, and means the fix is silently incomplete for Lombok-based Spring generation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/server/petstore/springboot-x-implements-skip/src/main/java/org/openapitools/model/ReadOnlyFirstDto.java, line 45:
<comment>The READ_ONLY annotations are only emitted in the non-Lombok getter/setter blocks of JavaSpring/pojo.mustache (lines 242 and 259). SpringCodegen exposes a real lombokAnnotations option (Getter/Setter/Data); for those configs the getter/setter (and thus any @JsonProperty) are not generated, so readOnly properties carry no READ_ONLY access and Jackson still accepts them during deserialization. This contradicts the PR's claim that the change prevents Jackson from accepting read-only properties in Spring models, and means the fix is silently incomplete for Lombok-based Spring generation.</comment>
<file context>
@@ -42,12 +42,12 @@ public ReadOnlyFirstDto bar(@Nullable String bar) {
@Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- @JsonProperty("bar")
+ @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
</file context>
| */ | ||
|
|
||
| @JsonProperty("snake_case") | ||
| @JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY) |
There was a problem hiding this comment.
P2: JsonProperty.Access.READ_ONLY on the setter makes Jackson skip the property entirely during deserialization (READ_ONLY disables the setter, not just the fluent method). Because the Spring generator emits one shared model used for both request and response DTOs, this also stops readOnly fields from being populated whenever that model is deserialized on the client side — e.g. a Spring WebClient or a shared DTO parsing a server response. Server-generated readOnly fields such as id or createdAt will come back null after this change, which the PR's "no breaking changes" claim does not cover. Note the Java generator deliberately avoids this by making readOnly setters private with an empty-JsonNullable path rather than annotating READ_ONLY. Confirm this template is only used where the model is never deserialized from responses; otherwise the READ_ONLY scope needs to be applied so readOnly response fields are still readable.
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-useHttpServiceProxyFactoryInterfacesConfigurator/src/main/java/org/openapitools/model/Name.java, line 79:
<comment>`JsonProperty.Access.READ_ONLY` on the setter makes Jackson skip the property entirely during deserialization (READ_ONLY disables the setter, not just the fluent method). Because the Spring generator emits one shared model used for both request and response DTOs, this also stops readOnly fields from being populated whenever that model is deserialized on the client side — e.g. a Spring WebClient or a shared DTO parsing a server response. Server-generated readOnly fields such as `id` or `createdAt` will come back null after this change, which the PR's "no breaking changes" claim does not cover. Note the `Java` generator deliberately avoids this by making readOnly setters `private` with an empty-`JsonNullable` path rather than annotating READ_ONLY. Confirm this template is only used where the model is never deserialized from responses; otherwise the READ_ONLY scope needs to be applied so readOnly response fields are still readable.</comment>
<file context>
@@ -76,12 +76,12 @@ public Name snakeCase(@Nullable Integer snakeCase) {
*/
- @JsonProperty("snake_case")
+ @JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer getSnakeCase() {
return snakeCase;
</file context>
|
|
||
| @Schema(name = "snake_case", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED) | ||
| @JsonProperty("snake_case") | ||
| @JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY) |
There was a problem hiding this comment.
P3: The access = JsonProperty.Access.READ_ONLY is emitted on both the getter and the setter. Jackson merges @JsonProperty access across all accessors of a logical property, so a single accessor annotation (getter or setter) is sufficient to make the property read-only; the duplicate on the second accessor is redundant and only adds noise to the generated public API. Emit it on one accessor per readOnly property.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/server/petstore/springboot-include-http-request-context/src/main/java/org/openapitools/model/NameDto.java, line 84:
<comment>The `access = JsonProperty.Access.READ_ONLY` is emitted on both the getter and the setter. Jackson merges `@JsonProperty` access across all accessors of a logical property, so a single accessor annotation (getter or setter) is sufficient to make the property read-only; the duplicate on the second accessor is redundant and only adds noise to the generated public API. Emit it on one accessor per readOnly property.</comment>
<file context>
@@ -81,12 +81,12 @@ public NameDto snakeCase(@Nullable Integer snakeCase) {
@Schema(name = "snake_case", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- @JsonProperty("snake_case")
+ @JsonProperty(value = "snake_case", access = JsonProperty.Access.READ_ONLY)
public @Nullable Integer getSnakeCase() {
return snakeCase;
</file context>
|
|
||
| @Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED) | ||
| @JsonProperty("bar") | ||
| @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY) |
There was a problem hiding this comment.
P3: The chain/fluent setters bar(String) and foo(String) are emitted with no Jackson annotation at all, while access = READ_ONLY is applied only to the getter and void setter. Whether a readOnly value is refused during request deserialization therefore depends entirely on Jackson merging the fluent setters into the same logical read-only property. Make the guarantee explicit by emitting @JsonProperty(value = "...", access = JsonProperty.Access.READ_ONLY) on the chain setters for readOnly vars in JavaSpring/pojo.mustache, so the enforcement is self-documenting and robust.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/server/petstore/springboot-builtin-validation/src/main/java/org/openapitools/model/HasOnlyReadOnly.java, line 45:
<comment>The chain/fluent setters `bar(String)` and `foo(String)` are emitted with no Jackson annotation at all, while `access = READ_ONLY` is applied only to the getter and void setter. Whether a readOnly value is refused during request deserialization therefore depends entirely on Jackson merging the fluent setters into the same logical read-only property. Make the guarantee explicit by emitting `@JsonProperty(value = "...", access = JsonProperty.Access.READ_ONLY)` on the chain setters for readOnly vars in JavaSpring/pojo.mustache, so the enforcement is self-documenting and robust.</comment>
<file context>
@@ -42,12 +42,12 @@ public HasOnlyReadOnly bar(@Nullable String bar) {
@Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- @JsonProperty("bar")
+ @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
</file context>
|
|
||
| @Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED) | ||
| @JsonProperty("bar") | ||
| @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY) |
There was a problem hiding this comment.
P3: This fix adds READ_ONLY access for readOnly properties, but the Spring generator does not handle the symmetric writeOnly case. The existing JavaJaxRS generators already emit access = JsonProperty.Access.WRITE_ONLY for writeOnly properties, while Spring emits nothing, so writeOnly properties (e.g. passwords, secrets) remain fully serialized into responses. Extend the same conditional handling to writeOnly to match the established JaxRS pattern and avoid leaking write-only data.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/server/petstore/springboot-spring-pageable-delegatePattern/src/main/java/org/openapitools/model/ReadOnlyFirst.java, line 43:
<comment>This fix adds READ_ONLY access for readOnly properties, but the Spring generator does not handle the symmetric writeOnly case. The existing JavaJaxRS generators already emit access = JsonProperty.Access.WRITE_ONLY for writeOnly properties, while Spring emits nothing, so writeOnly properties (e.g. passwords, secrets) remain fully serialized into responses. Extend the same conditional handling to writeOnly to match the established JaxRS pattern and avoid leaking write-only data.</comment>
<file context>
@@ -40,12 +40,12 @@ public ReadOnlyFirst bar(@Nullable String bar) {
@Schema(name = "bar", accessMode = Schema.AccessMode.READ_ONLY, requiredMode = Schema.RequiredMode.NOT_REQUIRED)
- @JsonProperty("bar")
+ @JsonProperty(value = "bar", access = JsonProperty.Access.READ_ONLY)
public @Nullable String getBar() {
return bar;
</file context>
Fixes #20612
Fixes generation of read-only properties in Spring models to include @JsonProperty(access = JsonProperty.Access.READ_ONLY) on setters.
This prevents Jackson from accepting read-only properties during deserialization, enforcing the OpenAPI contract.
Problem
Issue #20612: The OpenAPI Generator doesn't properly propagate readOnly property information. Generated setters for readOnly properties are treated as fully writable by Jackson,
violating the OpenAPI 3.0.0 specification.
Before this fix: