Skip to content

[kotlin-spring] Move cascading @Valid off containers and kotlin.Any properties to element types (HV000271/HV000272) - #25668

Open
Inf1mum wants to merge 2 commits into
OpenAPITools:masterfrom
Inf1mum:fix/25347-kotlin-spring-field-valid
Open

Inf1mum wants to merge 2 commits into
OpenAPITools:masterfrom
Inf1mum:fix/25347-kotlin-spring-field-valid

Conversation

@Inf1mum

@Inf1mum Inf1mum commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Description

kotlin-spring with useBeanValidation (the default) puts @field:Valid on container properties (List, Set, Map) and on free-form kotlin.Any properties. Hibernate Validator 9.1 (shipped with Spring Boot 4.1) deprecated container-level @Valid and logs HV000271/HV000272 while building bean metadata. Because the bean metadata cache is backed by soft references, the warnings are not only logged at startup: they come back every time the metadata of a rarely used model is evicted and rebuilt, so production logs end up with a steady stream of them.

This PR applies to the Kotlin templates what the Java generators got in #24176 (issue #23614, released in 7.26.0):

Since the Kotlin compiler only writes type-use annotations into bytecode when compiled with -Xemit-jvm-type-annotations, note that the generated Gradle and Maven builds already set this flag when useBeanValidation is enabled (added in #25592). If you compile the generated sources with your own build, keep setting that flag - without it, cascading into elements silently stops (same situation as the array/set element annotations introduced in #25592).

Generated output change

// before
data class Pet(
    @field:Valid
    @get:JsonProperty("items") val items: kotlin.collections.List<Item>? = null,

    @field:Valid
    @get:JsonProperty("customInfo") val customInfo: kotlin.Any? = null
)

// after
data class Pet(
    @get:JsonProperty("items") val items: kotlin.collections.List<@Valid Item>? = null,

    @get:JsonProperty("customInfo") val customInfo: kotlin.Any? = null
)

which matches the expected output from the issue.

Testing

  • KotlinSpringServerCodegenTest: updated the collection element annotation tests for the new map value rendering, and added fieldValidOnlyOnBeanTypedProperties, which asserts @field:Valid is present on a $ref-typed property and absent on free-form object, {} and free-form map properties.
  • mvn -pl modules/openapi-generator test: green (5756 tests, 0 failures).
  • Regenerated all kotlin-spring samples with bin/generate-samples.sh bin/configs/kotlin-spring-*.yaml: 35 deprecated @field:Valid annotations removed across 34 model files, no other diff.
  • Compiled the regenerated kotlin-springboot sample with Gradle and checked the compiled model with javap: the field carries javax.validation.Valid in RuntimeVisibleTypeAnnotations at TYPE_ARGUMENT(0), which is exactly what Hibernate Validator 9.x reads for cascading into elements, and which no longer logs HV000271.

PR checklist

  • Read the contribution guidelines.
  • Ran the sample generation scripts under ./bin/ to update the kotlin-spring Petstore samples so that CIs can verify the change.
  • Filed the PR against the correct branch: master.

Fixes #25347


Summary by cubic

Fixes #25347. kotlin-spring no longer emits the deprecated container-level @field:Valid on List/Set/Map properties, which Hibernate Validator 9.1+ (Spring Boot 4.1) warns about with HV000271/HV000272. Cascading is preserved through element-type annotations instead (List<@Valid T>, Map<String, @Valid V>), and bean-typed properties keep @field:Valid.

Free-form object/{} properties rendered as kotlin.Any lose the annotation since there is nothing to cascade into. Map value cascading is preserved via the type argument, unlike the closed attempt #24029 which dropped it entirely.

Type-use annotations are only visible to Hibernate Validator when compiled with -Xemit-jvm-type-annotations; the generated Gradle/Maven builds already set this flag when useBeanValidation is enabled.

Written for commit ca40fce. Summary will update on new commits.

View guided diff

…ement types (HV000271/HV000272)

What:
- kotlin-spring model properties no longer get the deprecated container-level
  `@field:Valid`: containers (List/Set/Map) cascade through element-type
  annotations instead (`List<@Valid T>`, `Map<String, @Valid V>`), and free-form/
  Any-typed properties (rendered as kotlin.Any) carry no `@Valid` at all.
  Bean-typed properties keep cascading via `@field:Valid`.

Why:
Hibernate Validator 9.1 (Spring Boot 4.1) deprecates `@Valid` on containers and
logs HV000271/HV000272 while building bean metadata; since the bean metadata
cache uses soft references, the warnings repeat every time a rarely used's
model metadata is evicted, producing a steady stream of warnings in production
logs. Fixes OpenAPITools#25347. The Java generators fixed the same problem in OpenAPITools#24176
(issue OpenAPITools#23614) by moving `@Valid` to the type argument. The closed attempt
OpenAPITools#24029 dropped `@Valid` from map properties entirely, silently losing cascading
validation of map values; here cascading is preserved through the type
argument instead. The `-Xemit-jvm-type-annotations` flag needed for Kotlin
type-use annotations to be visible to Hibernate Validator is already emitted
into the generated Gradle/Maven builds when useBeanValidation is enabled.

How:
- beanValidation.mustache: remove the container branch; guard the remaining
  non-container branch with isFreeFormObject/isAnyType so kotlin.Any fields
  are left unannotated.
- KotlinSpringServerCodegen: opt into map value type annotations by returning
  useBeanValidation from useBeanValidationOnMapValueType(), reusing the
  element annotation infrastructure (getAnnotatedItemsTypeDeclaration).
- tests: update the collection element annotation expectations for map values
  and add fieldValidOnlyOnBeanTypedProperties covering bean-typed, free-form,
  AnyType and free-form map properties; regenerate all kotlin-spring samples
  (35 deprecated annotations removed across 34 model files).

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

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. 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:8482">
P3: The comment claims exactly one `@field:Valid` remains, but this test never counts them. Either assert the count (e.g. with `countOccurrences`) or reword the comment to describe only the negative checks.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/kotlin-spring/beanValidation.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/kotlin-spring/beanValidation.mustache:2">
P2: The new `isAnyType` guard also matches inline `oneOf`/`anyOf`/`allOf` bean properties, so those properties lose `@field:Valid` and their nested constraints are no longer cascaded. Exclude only free-form/arbitrary `kotlin.Any` properties, or add a composed-model exception.</violation>
</file>

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

View guided diff | Re-trigger cubic

}}{{^isContainer}}{{^isPrimitiveType}}{{^isNumber}}{{^isUuid}}{{^isDateTime}}
@field:Valid{{/isDateTime}}{{/isUuid}}{{/isNumber}}{{/isPrimitiveType}}{{/isContainer}} No newline at end of file
{{! @field:Valid cascades only into bean-typed properties. Containers (List/Set/Map) cascade via element-type annotations instead (e.g. List<@Valid T>, Map<String, @Valid V>) - a container-level @Valid is deprecated by Hibernate Validator 9.1+ and logs HV000271, and free-form / Any-typed properties render as kotlin.Any with nothing to cascade into (HV000272). }}{{!
}}{{^isContainer}}{{^isPrimitiveType}}{{^isNumber}}{{^isUuid}}{{^isDateTime}}{{^isFreeFormObject}}{{^isAnyType}}

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 isAnyType guard also matches inline oneOf/anyOf/allOf bean properties, so those properties lose @field:Valid and their nested constraints are no longer cascaded. Exclude only free-form/arbitrary kotlin.Any properties, or add a composed-model exception.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At modules/openapi-generator/src/main/resources/kotlin-spring/beanValidation.mustache, line 2:

<comment>The new `isAnyType` guard also matches inline `oneOf`/`anyOf`/`allOf` bean properties, so those properties lose `@field:Valid` and their nested constraints are no longer cascaded. Exclude only free-form/arbitrary `kotlin.Any` properties, or add a composed-model exception.</comment>

<file context>
@@ -1,4 +1,3 @@
-    @field:Valid{{/isDateTime}}{{/isUuid}}{{/isNumber}}{{/isPrimitiveType}}{{/isContainer}}
\ No newline at end of file
+{{! @field:Valid cascades only into bean-typed properties. Containers (List/Set/Map) cascade via element-type annotations instead (e.g. List<@Valid T>, Map<String, @Valid V>) - a container-level @Valid is deprecated by Hibernate Validator 9.1+ and logs HV000271, and free-form / Any-typed properties render as kotlin.Any with nothing to cascade into (HV000272). }}{{!
+}}{{^isContainer}}{{^isPrimitiveType}}{{^isNumber}}{{^isUuid}}{{^isDateTime}}{{^isFreeFormObject}}{{^isAnyType}}
+    @field:Valid{{/isAnyType}}{{/isFreeFormObject}}{{/isDateTime}}{{/isUuid}}{{/isNumber}}{{/isPrimitiveType}}{{/isContainer}}
\ No newline at end of file
</file context>

"val anything: kotlin.Any? = null");
// free-form map values render as kotlin.Any without any @Valid
assertFileContains(holder, "val anyMap: kotlin.collections.Map<kotlin.String, kotlin.Any>? = null");
// exactly one @field:Valid remains (singlePet); containers and Any-typed fields carry none

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.

P3: The comment claims exactly one @field:Valid remains, but this test never counts them. Either assert the count (e.g. with countOccurrences) or reword the comment to describe only the negative checks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java, line 8482:

<comment>The comment claims exactly one `@field:Valid` remains, but this test never counts them. Either assert the count (e.g. with `countOccurrences`) or reword the comment to describe only the negative checks.</comment>

<file context>
@@ -8455,10 +8455,36 @@ public void collectionItemsGetBeanValidationOnElementType() throws IOException {
+                "val anything: kotlin.Any? = null");
+        // free-form map values render as kotlin.Any without any @Valid
+        assertFileContains(holder, "val anyMap: kotlin.collections.Map<kotlin.String, kotlin.Any>? = null");
+        // exactly one @field:Valid remains (singlePet); containers and Any-typed fields carry none
+        assertFileNotContains(holder,
+                "@field:Valid\n    @Schema(description = \"\")\n    @param:JsonProperty(\"pets\")",
</file context>
Suggested change
// exactly one @field:Valid remains (singlePet); containers and Any-typed fields carry none
// containers and Any-typed fields carry no @field:Valid; singlePet keeps it (checked above)

… Central 429

The "Build Kotlin server (jdk21) (samples/server/others/kotlin-springboot/oneOf-discriminator)"
check (run 43515494934, job 132196316812) failed without compiling anything:
the "Install Gradle wrapper" step died during Gradle configuration while
resolving kotlin-gradle-plugin:1.9.25 / spring-boot-gradle-plugin:3.3.13,
with repeated "Received status code 429 from server: Too Many Requests"
from repo.maven.apache.org / plugins.gradle.org (5 occurrences).

No code defect behind the red check:
- the same sample is green on jdk17 for the same head SHA (job 132196323194);
- sibling oneOf-discriminator samples pass on jdk21;
- -Xemit-jvm-type-annotations is present in the sample's generated
  build.gradle.kts and pom.xml;
- regenerating the sample produces zero diff (Samples up-to-date is green);
- a local Gradle 8.14.4 + JDK 21 build of the sample succeeds.

This empty commit only re-runs the CI suite on a fresh head SHA; no changes
to templates, codegen or samples.

Signed-off-by: Inf1mum <vadimlazuk5@gmail.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG][KOTLIN-SPRING] @field:Valid on container properties triggers HV000271/HV000272 warnings (Hibernate Validator 9.1)

1 participant