Repository navigation
[kotlin-spring] Bean Validation and custom annotations on collection element types - #25592
Conversation
Add bean validation (@Valid, @SiZe, @pattern, @Email, @Min/@max, @DecimalMin/@DecimalMax) to list/set element types when useBeanValidation is enabled, following the Java generators' rules. Also apply x-field-extra-annotation declared on items and the new additionalItemsAnnotations option to element types. The behaviour is opt-in through no-op hooks in AbstractKotlinCodegen that only KotlinSpringServerCodegen overrides, so other Kotlin generators are unchanged. Validation imports are added to the service, serviceImpl, apiDelegate and api_test templates, and enum-item extra annotations are kept in the model templates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nerate samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
4 issues found across 245 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="samples/server/petstore/kotlin-spring-cloud-4/src/main/kotlin/org/openapitools/api/PetApi.kt">
<violation number="1" location="samples/server/petstore/kotlin-spring-cloud-4/src/main/kotlin/org/openapitools/api/PetApi.kt:71">
P2: These annotations are nested inside `ResponseEntity`, which Bean Validation does not traverse as a container, so the generated endpoints still will not validate returned `Pet` elements at runtime. Apply validation to an actually traversed response body or configure a supported value extractor for the response wrapper.</violation>
</file>
<file name="samples/server/petstore/kotlin-springboot-collection-item-annotations/build.gradle.kts">
<violation number="1" location="samples/server/petstore/kotlin-springboot-collection-item-annotations/build.gradle.kts:16">
P1: This Gradle build cannot be evaluated because `plugins {}` appears after regular build logic and uses a local version variable inside the constrained plugin DSL. Move the block to the top and use literal versions (or declare them through `pluginManagement`).</violation>
</file>
<file name="samples/server/petstore/kotlin-springboot-no-response-entity/src/main/kotlin/org/openapitools/api/PetApiController.kt">
<violation number="1" location="samples/server/petstore/kotlin-springboot-no-response-entity/src/main/kotlin/org/openapitools/api/PetApiController.kt:66">
P2: These annotations are retained only in Kotlin source for this sample, so Spring/Hibernate Validator cannot cascade validation into the returned `Pet` elements at runtime. Add `-Xemit-jvm-type-annotations` to the sample's Kotlin compiler arguments (and the generated sample configuration) so the new return-value validation works.</violation>
</file>
<file name="samples/server/petstore/kotlin-spring-default/src/main/kotlin/org/openapitools/api/PetApiController.kt">
<violation number="1" location="samples/server/petstore/kotlin-spring-default/src/main/kotlin/org/openapitools/api/PetApiController.kt:96">
P2: The new `@Valid` is nested inside `ResponseEntity`, so method validation cannot unwrap the response wrapper and traverse the `List` elements. Generate a return shape that exposes `List<@Valid Pet>` directly, or provide an explicit `ResponseEntity` value extractor/response-validation path; otherwise these annotations do not enable the advertised runtime validation.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
View guided diff | Re-trigger cubic
| kotlinOptions.jvmTarget = "17" | ||
| } | ||
|
|
||
| plugins { |
There was a problem hiding this comment.
P1: This Gradle build cannot be evaluated because plugins {} appears after regular build logic and uses a local version variable inside the constrained plugin DSL. Move the block to the top and use literal versions (or declare them through pluginManagement).
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 samples/server/petstore/kotlin-springboot-collection-item-annotations/build.gradle.kts, line 16:
<comment>This Gradle build cannot be evaluated because `plugins {}` appears after regular build logic and uses a local version variable inside the constrained plugin DSL. Move the block to the top and use literal versions (or declare them through `pluginManagement`).</comment>
<file context>
@@ -0,0 +1,45 @@
+ kotlinOptions.jvmTarget = "17"
+}
+
+plugins {
+ val kotlinVersion = "1.9.25"
+ id("org.jetbrains.kotlin.jvm") version kotlinVersion
</file context>
There was a problem hiding this comment.
Not valid for the Kotlin DSL: plugins {} is extracted and evaluated first regardless of its position, and a local val inside the block is supported. The layout comes unchanged from the existing template (only the compiler flag was added), and sh ./gradlew help evaluates this script successfully.
…runtime - Pass -Xemit-jvm-type-annotations in generated Maven/Gradle builds when useBeanValidation is enabled, so Bean Validation sees List<@Valid Pet>. - Keep element annotations out of return types: Hibernate Validator cannot extract values from ResponseEntity/Flow (HV000197). - Import only the validation annotations used on element types in service/delegate/test files. - spring-boot README: mention the Gradle wrapper only for Gradle builds. - Clarify additionalItemsAnnotations nullability wording. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
2 issues found across 279 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="samples/server/petstore/kotlin-springboot-modelMutable/README.md">
<violation number="1" location="samples/server/petstore/kotlin-springboot-modelMutable/README.md:16">
P2: `./gradlew` is not executable in this sample: `gradlew` is tracked with mode `100644`, so this command fails with permission denied on POSIX checkouts. Mark `gradlew` executable in Git (mode `100755`).</violation>
</file>
<file name="samples/server/petstore/kotlin-springboot-bigdecimal-default/README.md">
<violation number="1" location="samples/server/petstore/kotlin-springboot-bigdecimal-default/README.md:7">
P2: `./gradlew` is not executable in this sample, so the documented Gradle build fails with `Permission denied` on POSIX systems. Mark `gradlew` executable in Git (mode `100755`) before documenting direct execution.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
View guided diff | Re-trigger cubic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
All reported issues were addressed across 279 files
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
View guided diff | Re-trigger cubic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ae48be6 to
6713ddd
Compare
There was a problem hiding this comment.
3 issues found across 279 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="samples/server/petstore/kotlin-springboot-integer-enum/README.md">
<violation number="1" location="samples/server/petstore/kotlin-springboot-integer-enum/README.md:16">
P2: This Gradle command cannot start the documented server: the build disables `bootJar` and the sample has no application `main` class, so the produced JAR has no executable entry point. Enable and configure `bootJar` or remove the `java -jar` step and the claim that this build runs the server.</violation>
</file>
<file name="samples/server/petstore/kotlin-spring-oneof-jackson-default-impl/README.md">
<violation number="1" location="samples/server/petstore/kotlin-spring-oneof-jackson-default-impl/README.md:16">
P2: This Gradle command cannot start the sample: the project has no application entry point and disables `bootJar`, so the generated JAR is not executable. Add/configure a Spring Boot main class and enable `bootJar`, or remove the `java -jar` launch step and update the surrounding startup guidance.</violation>
</file>
<file name="samples/server/others/kotlin-springboot/oneOf-discriminator/README.md">
<violation number="1" location="samples/server/others/kotlin-springboot/oneOf-discriminator/README.md:16">
P2: This new Gradle command cannot start the advertised server: the build disables `bootJar`, and this sample has no application entry point or executable JAR manifest. Either add/configure a Spring Boot main application and enable `bootJar`, or remove the `java -jar` launch instruction.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
View guided diff | Re-trigger cubic
| To build the project using gradle on Linux or macOS, run: | ||
| ```bash | ||
| gradle build && java -jar build/libs/openapi-spring-1.0.0.jar | ||
| sh ./gradlew build && java -jar build/libs/openapi-spring-1.0.0.jar |
There was a problem hiding this comment.
P2: This Gradle command cannot start the documented server: the build disables bootJar and the sample has no application main class, so the produced JAR has no executable entry point. Enable and configure bootJar or remove the java -jar step and the claim that this build runs the server.
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 samples/server/petstore/kotlin-springboot-integer-enum/README.md, line 16:
<comment>This Gradle command cannot start the documented server: the build disables `bootJar` and the sample has no application `main` class, so the produced JAR has no executable entry point. Enable and configure `bootJar` or remove the `java -jar` step and the claim that this build runs the server.</comment>
<file context>
@@ -4,18 +4,21 @@ This Kotlin based [Spring Boot](https://spring.io/projects/spring-boot) applicat
+To build the project using gradle on Linux or macOS, run:
```bash
-gradle build && java -jar build/libs/openapi-spring-1.0.0.jar
+sh ./gradlew build && java -jar build/libs/openapi-spring-1.0.0.jar
+```
+
</file context>
There was a problem hiding this comment.
Valid, but pre-existing and out of scope: for interfaceOnly=true projects there is no Application.kt, bootJar is disabled and the Maven boot plugin is omitted, so the upstream java -jar instructions (Maven and Gradle) never worked. This PR no longer touches the README: I reverted all README template changes to keep it focused on collection element annotations. The interfaceOnly-aware wording will go into a separate docs PR.
| To build the project using gradle on Linux or macOS, run: | ||
| ```bash | ||
| gradle build && java -jar build/libs/kotlin-spring-oneof-jackson-default-impl-1.0.0.jar | ||
| sh ./gradlew build && java -jar build/libs/kotlin-spring-oneof-jackson-default-impl-1.0.0.jar |
There was a problem hiding this comment.
P2: This Gradle command cannot start the sample: the project has no application entry point and disables bootJar, so the generated JAR is not executable. Add/configure a Spring Boot main class and enable bootJar, or remove the java -jar launch step and update the surrounding startup guidance.
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 samples/server/petstore/kotlin-spring-oneof-jackson-default-impl/README.md, line 16:
<comment>This Gradle command cannot start the sample: the project has no application entry point and disables `bootJar`, so the generated JAR is not executable. Add/configure a Spring Boot main class and enable `bootJar`, or remove the `java -jar` launch step and update the surrounding startup guidance.</comment>
<file context>
@@ -4,18 +4,21 @@ This Kotlin based [Spring Boot](https://spring.io/projects/spring-boot) applicat
+To build the project using gradle on Linux or macOS, run:
```bash
-gradle build && java -jar build/libs/kotlin-spring-oneof-jackson-default-impl-1.0.0.jar
+sh ./gradlew build && java -jar build/libs/kotlin-spring-oneof-jackson-default-impl-1.0.0.jar
+```
+
</file context>
There was a problem hiding this comment.
Valid, but pre-existing and out of scope: for interfaceOnly=true projects there is no Application.kt, bootJar is disabled and the Maven boot plugin is omitted, so the upstream java -jar instructions (Maven and Gradle) never worked. This PR no longer touches the README: I reverted all README template changes to keep it focused on collection element annotations. The interfaceOnly-aware wording will go into a separate docs PR.
| To build the project using gradle on Linux or macOS, run: | ||
| ```bash | ||
| gradle build && java -jar build/libs/openapi-spring-1.0.0.jar | ||
| sh ./gradlew build && java -jar build/libs/openapi-spring-1.0.0.jar |
There was a problem hiding this comment.
P2: This new Gradle command cannot start the advertised server: the build disables bootJar, and this sample has no application entry point or executable JAR manifest. Either add/configure a Spring Boot main application and enable bootJar, or remove the java -jar launch instruction.
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 samples/server/others/kotlin-springboot/oneOf-discriminator/README.md, line 16:
<comment>This new Gradle command cannot start the advertised server: the build disables `bootJar`, and this sample has no application entry point or executable JAR manifest. Either add/configure a Spring Boot main application and enable `bootJar`, or remove the `java -jar` launch instruction.</comment>
<file context>
@@ -4,18 +4,21 @@ This Kotlin based [Spring Boot](https://spring.io/projects/spring-boot) applicat
+To build the project using gradle on Linux or macOS, run:
```bash
-gradle build && java -jar build/libs/openapi-spring-1.0.0.jar
+sh ./gradlew build && java -jar build/libs/openapi-spring-1.0.0.jar
+```
+
</file context>
There was a problem hiding this comment.
Valid, but pre-existing and out of scope: for interfaceOnly=true projects there is no Application.kt, bootJar is disabled and the Maven boot plugin is omitted, so the upstream java -jar instructions (Maven and Gradle) never worked. This PR no longer touches the README: I reverted all README template changes to keep it focused on collection element annotations. The interfaceOnly-aware wording will go into a separate docs PR.
README wording issues (wrapper usage, POSIX/Windows commands, interfaceOnly projects) predate this PR and will be addressed in a separate docs PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… template" This reverts commit 6bd2092.
…m template" This reverts commit 5fb94aa.
There was a problem hiding this comment.
No issues found across 249 files
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
View guided diff | Re-trigger cubic
Today kotlin-spring ignores constraints on array
items:With this PR (and
useBeanValidation=true):This follows the existing Java logic in
AbstractJavaCodegen(getTypeDeclarationWithBeanValidation/getBeanValidation/getExtraListAnnotation).What changes
AbstractKotlinCodegen.getTypeDeclaration(): for arrays and sets, the element type is rendered as<extra annotations> <bean validation> Type[?]. The protected hooks behind it are no-ops by default, so other Kotlin generators (kotlin client, kotlin-server, kotlin-wiremock, …) produce identical output. OnlyKotlinSpringServerCodegenoverrides them. AuseBeanValidationOnMapValueType()hook (defaultfalse) is ready for a follow-up on map values.useBeanValidation=true):$refto object or composed (oneOf/anyOf/allOf, including normalizer-wrappedallOf: [$ref]) models →@Valid@Size,@Pattern,@Email,@Min,@Max,@DecimalMin,@DecimalMaxx-field-extra-annotationonitems(string or list) is added to the element type. This also works for inline enum items.additionalItemsAnnotations(kotlin-spring):;- or newline-separated annotations added to every collection element type. Unlike Java,@NotNullis not removed fornullable: trueitems. In Kotlin, element nullability is expressed by?, which the compiler checks and Jackson 3's Kotlin module enforces on deserialization.List<List<@Size(max = 3) kotlin.String>>.List<@Valid Pet?>.@Size(max = 3) kotlin.String). Kotlin doesn't need Java'sjava.lang.@Size Stringplacement.Making it effective at runtime
-Xemit-jvm-type-annotationswhenuseBeanValidation=true. The option description tells users with their own build to add it.ResponseEntity<List<@Valid Pet>>made every call fail with HV000197 (noValueExtractorforResponseEntity, see@NotNullon a web endpoint generic type parameter fails spring-projects/spring-framework#32375). Element annotations are therefore not added when rendering response types, so return signatures stay as they were before this PR.Unchanged on purpose
@field:Validon container properties,@get:SizeforminItems/maxItems,@Valid @RequestBody/@RequestParamon parameters.Flow<{{{baseType}}}>and stay without element annotations.dataTypealready produces are handled in [JAVA-SPRING;KOTLIN-SPRING] feature:x-field-extra-annotationparity for kotlin-spring params + newx-request-body-extra-annotation+--inject-operation-vendor-extensions#24780.dataTypeauditI checked the places where kotlin-spring uses
dataType/returnTypeas something other than a type: default values ({{{dataType}}}("…")is scalar-only),@ApiResponse(implementation = X::class)(usesbaseType), sealed/oneOf interfaces (baseType),x-kotlin-implementsoverrides (same types on both sides), the reactivex-reactive-array-string-returncheck, and enum-array branches. Inline enum arrays bypasseddataType, so I fixed them indataClassReqVar/dataClassOptVar.Imports
javax/jakarta.validation.*wildcard imports already cover the new annotations.beanValidationImports.mustachepartial imports only the annotations actually used on element types in that file. Operationx-extra-importsare added there when signatures contain custom element annotations.x-*-messagevalues are escaped as Kotlin string literals.Docs
docs/generators/kotlin-spring.md: newadditionalItemsAnnotationsoption.x-field-extra-annotationdescription inVendorExtensionnow says it applies to the element type onitems. Because the vendor extension description is shared, this regenerates one line in several Java/Spring/Kotlin generator docs.Samples
bin/configs/kotlin-spring-boot-collection-item-annotations.yamlwith its own annotated spec copy (petstore-collection-item-annotations.yaml), added to thesamples-kotlin-server-jdk17workflow matrix.Testing
./mvnw -q -pl modules/openapi-generator test -Dtest='KotlinSpringServerCodegenTest' -Dsurefire.failIfNoSpecifiedTests=false -Djacoco.skip=true→ passed (kotlin.spring.KotlinSpringServerCodegenTest: 309 tests,kotlin.KotlinSpringServerCodegenTest: 12 tests, 0 failures)$ref/oneOf/allOf/allOf:[$ref]items, all constraint annotations,uniqueItemssets, enum/free-form items, nullable items, nested arrays,x-field-extra-annotation(string/list),additionalItemsAnnotationsalone and combined,useBeanValidation=false, response types without element annotations, imports in delegate/service files, and unchanged kotlin client output.PR checklist
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.
Summary by cubic
Makes bean validation actually apply to list/set element types in the Kotlin Spring server generator, so nested collection items are validated at runtime instead of only the collection itself.
@Valid,@Size,@Pattern,@Email,@Min/@Max, and@DecimalMin/@DecimalMaxfrom arrayitemsschemas to the element type (e.g.List<@Valid Pet>), plusx-field-extra-annotationand a newadditionalItemsAnnotationsoption for element-type annotations.AbstractKotlinCodegen; onlyKotlinSpringServerCodegenoverrides them, so other Kotlin generators are unchanged.-Xemit-jvm-type-annotationswhenuseBeanValidationis enabled so the annotations reach bytecode, and element annotations are kept out of return types because Hibernate Validator can't extract values fromResponseEntity/Flow.Written for commit 0319295. Summary will update on new commits.