[kotlin-spring] Bean Validation and custom annotations on collection element types
Today kotlin-spring ignores constraints on array items:
val tags: kotlin.collections.List<kotlin.String>? = null // items: maxLength: 10 is ignored
val pets: kotlin.collections.List<Pet>? = null // nested Pet is never validated
With this PR (and useBeanValidation=true):
val tags: kotlin.collections.List<@Size(max = 10) kotlin.String>? = null
val pets: kotlin.collections.List<@Valid Pet>? = null
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.- Bean validation on elements (
useBeanValidation=true):$refto object or composed (oneOf/anyOf/allOf, including normalizer-wrappedallOf: [$ref]) models →@Valid- string / number / integer / long constraints →
@Size,@Pattern,@Email,@Min,@Max,@DecimalMin,@DecimalMax - enum and free-form object items → no annotation
x-field-extra-annotationonitems(string or list) is added to the element type. This also works for inline enum items.- New option
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. - Nested collections work recursively:
List<List<@Size(max = 3) kotlin.String>>. - Nullability is kept:
List<@Valid Pet?>. - Annotations go before qualified names (
@Size(max = 3) kotlin.String). Kotlin doesn't need Java'sjava.lang.@Size Stringplacement.
Making it effective at runtime
- By default the Kotlin compiler does not write type-use annotations to bytecode. All kotlin-spring Maven/Gradle templates (spring-boot, spring-cloud, spring-declarative-http-interface; SB2/3/4) now pass
-Xemit-jvm-type-annotationswhenuseBeanValidation=true. The option description tells users with their own build to add it. - Verified at runtime on the Spring Boot 3 sample: element constraints on model properties (including nested lists/sets) and on controller list parameters are enforced.
- Response types carry no element annotations. With visible type annotations,
ResponseEntity<List<@Valid Pet>>made every call fail with HV000197 (noValueExtractorforResponseEntity, see 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
- Existing container-level annotations:
@field:Validon container properties,@get:SizeforminItems/maxItems,@Valid @RequestBody/@RequestParamon parameters. - Reactive request bodies render
Flow<{{{baseType}}}>and stay without element annotations. - Annotations on API parameters/request bodies beyond what
dataTypealready produces are handled in #24780.
dataType audit
I checked the places where kotlin-spring uses dataType/returnType as something other than a type: default values ({{{dataType}}}("…") is scalar-only), @ApiResponse(implementation = X::class) (uses baseType), sealed/oneOf interfaces (baseType), x-kotlin-implements overrides (same types on both sides), the reactive x-reactive-array-string-return check, and enum-array branches. Inline enum arrays bypassed dataType, so I fixed them in dataClassReqVar/dataClassOptVar.
Imports
- Models/APIs: existing
javax/jakarta.validation.*wildcard imports already cover the new annotations. - delegate / service / serviceImpl / api_test: a new
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
- New config
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. - I regenerated all Kotlin configs. Only kotlin-spring samples changed: build files (compiler flag) and element-annotated types/imports.
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)- New tests cover
$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. - Samples compiled locally: the new collection sample plus delegate, reactive, swagger2, openApiNullable+modelMutable, spring-cloud, declarative-interface, SB2 (javax), SB3, SB4.
PR checklist
- Read the contribution guidelines.
- Run the following to build the project and update samples:
(For Windows users, please run the script in WSL)./mvnw clean package || exit ./bin/generate-samples.sh ./bin/configs/*.yaml || exit ./bin/utils/export_docs_generators.sh || exit
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
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.
- Applies
@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. - Opt-in via no-op hooks in
AbstractKotlinCodegen; onlyKotlinSpringServerCodegenoverrides them, so other Kotlin generators are unchanged. - Generated Maven/Gradle builds pass
-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. - Regenerates Kotlin Spring samples and adds a collection-item-annotation sample config.