From 6c06df4959ab52654fec43aeda992668f213f61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecs=C3=A9rke?= Date: Fri, 5 Jun 2026 11:15:44 +0200 Subject: [PATCH 1/3] fix(kotlin-spring): documented option `useSpringBuiltInValidation` has no effect (#23950) --- .../languages/KotlinSpringServerCodegen.java | 7 ++ .../main/resources/kotlin-spring/api.mustache | 8 +- .../kotlin-spring/apiInterface.mustache | 8 +- .../kotlin/KotlinSpringServerCodegenTest.java | 97 +++++++++++++++++++ 4 files changed, 108 insertions(+), 12 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java index dba07bae883e..3b706d521af1 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java @@ -98,6 +98,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen public static final String USE_SPRING_BOOT3 = "useSpringBoot3"; public static final String USE_SPRING_BOOT4 = "useSpringBoot4"; + public static final String USE_SPRING_BUILT_IN_VALIDATION = "useSpringBuiltInValidation"; public static final String INCLUDE_HTTP_REQUEST_CONTEXT = "includeHttpRequestContext"; public static final String USE_FLOW_FOR_ARRAY_RETURN_TYPE = "useFlowForArrayReturnType"; public static final String REQUEST_MAPPING_OPTION = "requestMappingMode"; @@ -190,6 +191,8 @@ public String getDescription() { protected boolean useSpringBoot3 = false; @Getter @Setter protected boolean useSpringBoot4 = false; + @Getter @Setter + protected boolean useSpringBuiltInValidation = false; protected RequestMappingMode requestMappingMode = RequestMappingMode.controller; private DocumentationProvider documentationProvider; private AnnotationLibrary annotationLibrary; @@ -286,6 +289,7 @@ public KotlinSpringServerCodegen() { " (contexts) added to single project.", beanQualifiers); addSwitch(USE_SPRING_BOOT3, "Generate code and provide dependencies for use with Spring Boot ≥ 3 (use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.", useSpringBoot3); addSwitch(USE_SPRING_BOOT4, "Generate code and provide dependencies for use with Spring Boot 4.x. Enabling this option will also enable `useJakartaEe`.", useSpringBoot4); + addSwitch(USE_SPRING_BUILT_IN_VALIDATION, "Disable `@Validated` at the class level when using built-in validation.", useSpringBuiltInValidation); addSwitch(USE_JACKSON_3, "Use Jackson 3 dependencies (tools.jackson package). Only available with `useSpringBoot4`. Defaults to true when `useSpringBoot4` is enabled.", useJackson3); addSwitch(USE_FLOW_FOR_ARRAY_RETURN_TYPE, "Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.", useFlowForArrayReturnType); addSwitch(INCLUDE_HTTP_REQUEST_CONTEXT, "Whether to include HttpServletRequest (blocking) or ServerWebExchange (reactive) as additional parameter in generated methods.", includeHttpRequestContext); @@ -521,6 +525,9 @@ public void processOpts() { if (additionalProperties.containsKey(USE_SPRING_BOOT4)) { this.setUseSpringBoot4(convertPropertyToBoolean(USE_SPRING_BOOT4)); } + if (additionalProperties.containsKey(USE_SPRING_BUILT_IN_VALIDATION)) { + this.setUseSpringBuiltInValidation(convertPropertyToBoolean(USE_SPRING_BUILT_IN_VALIDATION)); + } if (additionalProperties.containsKey(INCLUDE_HTTP_REQUEST_CONTEXT)) { this.setIncludeHttpRequestContext(convertPropertyToBoolean(INCLUDE_HTTP_REQUEST_CONTEXT)); } diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache index 2b7662531379..b7969d3efb66 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache @@ -29,9 +29,7 @@ import org.springframework.http.ResponseEntity {{/useResponseEntity}} import org.springframework.web.bind.annotation.* -{{#useBeanValidation}} -import org.springframework.validation.annotation.Validated -{{/useBeanValidation}} +{{#useBeanValidation}}{{^useSpringBuiltInValidation}}import org.springframework.validation.annotation.Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} import org.springframework.web.context.request.NativeWebRequest import org.springframework.beans.factory.annotation.Autowired @@ -54,9 +52,7 @@ import kotlin.collections.List import kotlin.collections.Map @RestController{{#beanQualifiers}}("{{package}}.{{classname}}Controller"){{/beanQualifiers}} -{{#useBeanValidation}} -@Validated -{{/useBeanValidation}} +{{#useBeanValidation}}{{^useSpringBuiltInValidation}}@Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} {{#swagger1AnnotationLibrary}} @Api(value = "{{{baseName}}}", description = "The {{{baseName}}} API") {{/swagger1AnnotationLibrary}} diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache index 82608294c6ba..1d4342480da6 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache @@ -34,9 +34,7 @@ import org.springframework.http.ResponseEntity {{/useResponseEntity}} import org.springframework.web.bind.annotation.* -{{#useBeanValidation}} -import org.springframework.validation.annotation.Validated -{{/useBeanValidation}} +{{#useBeanValidation}}{{^useSpringBuiltInValidation}}import org.springframework.validation.annotation.Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} import org.springframework.web.context.request.NativeWebRequest import org.springframework.beans.factory.annotation.Autowired @@ -61,9 +59,7 @@ import kotlin.collections.Map {{^useFeignClient}} @RestController {{/useFeignClient}} -{{#useBeanValidation}} -@Validated -{{/useBeanValidation}} +{{#useBeanValidation}}{{^useSpringBuiltInValidation}}@Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} {{#swagger1AnnotationLibrary}} @Api(value = "{{{baseName}}}", description = "The {{{baseName}}} API") {{/swagger1AnnotationLibrary}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java index e66638589e7a..96e82d56e864 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinSpringServerCodegenTest.java @@ -105,4 +105,101 @@ public void polymorphicJacksonSerialization() throws IOException { // derived doesn't contain disciminator TestUtils.assertFileNotContains(birdKt, "val discriminator"); } + + @Test(description = "should not generate @Validated annotation on interface when built-in validation option is set to true") + public void shouldEnableBuiltInValidationOptionWhenSetToTrueInterface() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ; + codegen.setOutputDir(output.getAbsolutePath()); + + codegen.setUseSpringBoot3(true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_BEANVALIDATION, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_SPRING_BUILT_IN_VALIDATION, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.INTERFACE_ONLY, true); + + new DefaultGenerator().opts( + new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/3_0/petstore.yaml")) + .config(codegen) + ).generate(); + + final Path userApiKt = Paths.get(output + "/src/main/kotlin/org/openapitools/api/UserApi.kt"); + TestUtils.assertFileNotContains(userApiKt, "import org.springframework.validation.annotation.Validated"); + TestUtils.assertFileNotContains(userApiKt, "@org.springframework.validation.annotation.Validated"); + TestUtils.assertFileNotContains(userApiKt, "@Validated"); + } + + @Test(description = "should not generate @Validated annotation on controller when built-in validation option is set to true") + public void shouldEnableBuiltInValidationOptionWhenSetToTrueController() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ; + codegen.setOutputDir(output.getAbsolutePath()); + + codegen.setUseSpringBoot3(true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_BEANVALIDATION, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_SPRING_BUILT_IN_VALIDATION, true); + + new DefaultGenerator().opts( + new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/3_0/petstore.yaml")) + .config(codegen) + ).generate(); + + final Path userApiKt = Paths.get(output + "/src/main/kotlin/org/openapitools/api/UserApiController.kt"); + TestUtils.assertFileNotContains(userApiKt, "import org.springframework.validation.annotation.Validated"); + TestUtils.assertFileNotContains(userApiKt, "@org.springframework.validation.annotation.Validated"); + TestUtils.assertFileNotContains(userApiKt, "@Validated"); + } + + @Test(description = "should generate @Validated annotation when built-in validation option is set to false") + public void shouldDisableBuiltInValidationOptionWhenSetToFalse() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ; + codegen.setOutputDir(output.getAbsolutePath()); + + codegen.setUseSpringBoot3(true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_BEANVALIDATION, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_SPRING_BUILT_IN_VALIDATION, false); + codegen.additionalProperties().put(KotlinSpringServerCodegen.INTERFACE_ONLY, true); + + new DefaultGenerator().opts( + new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/3_0/petstore.yaml")) + .config(codegen) + ).generate(); + + final Path userApiKt = Paths.get(output + "/src/main/kotlin/org/openapitools/api/UserApi.kt"); + TestUtils.assertFileContains(userApiKt, "import org.springframework.validation.annotation.Validated"); + TestUtils.assertFileContains(userApiKt, "@Validated"); + } + + @Test(description = "should generate @Validated annotation when built-in validation option has default value") + public void shouldDisableBuiltInValidationOptionByDefault() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen() ; + codegen.setOutputDir(output.getAbsolutePath()); + + codegen.additionalProperties().put(KotlinSpringServerCodegen.INTERFACE_ONLY, true); + + codegen.setUseSpringBoot3(true); + + new DefaultGenerator().opts( + new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/3_0/petstore.yaml")) + .config(codegen) + ).generate(); + + final Path userApiKt = Paths.get(output + "/src/main/kotlin/org/openapitools/api/UserApi.kt"); + TestUtils.assertFileContains(userApiKt, "import org.springframework.validation.annotation.Validated"); + TestUtils.assertFileContains(userApiKt, "@Validated"); + } + } From f9b1a9898b7f8c308374cb6cdd0d4a2583c7dd6d Mon Sep 17 00:00:00 2001 From: William Cheng Date: Wed, 24 Jun 2026 16:07:58 +0800 Subject: [PATCH 2/3] update doc, template --- docs/generators/kotlin-spring.md | 1 + .../src/main/resources/kotlin-spring/api.mustache | 12 ++++++++++-- .../resources/kotlin-spring/apiInterface.mustache | 12 ++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/generators/kotlin-spring.md b/docs/generators/kotlin-spring.md index bf53ef9945e8..a636658d17f4 100644 --- a/docs/generators/kotlin-spring.md +++ b/docs/generators/kotlin-spring.md @@ -71,6 +71,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useSealedResponseInterfaces|Generate sealed interfaces for endpoint responses that all possible response types implement. Allows controllers to return any valid response type in a type-safe manner (e.g., sealed interface CreateUserResponse implemented by User, ConflictResponse, ErrorResponse)| |false| |useSpringBoot3|Generate code and provide dependencies for use with Spring Boot ≥ 3 (use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false| |useSpringBoot4|Generate code and provide dependencies for use with Spring Boot 4.x. Enabling this option will also enable `useJakartaEe`.| |false| +|useSpringBuiltInValidation|Disable `@Validated` at the class level when using built-in validation.| |false| |useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true| |useTags|Whether to use tags for creating interface and controller class names| |false| |xKotlinImplementsFieldsSkip|A list of fields per schema name that should NOT be created with `override` keyword despite their presence in vendor extension `x-kotlin-implements-fields` for the schema. Example: yaml `xKotlinImplementsFieldsSkip: Pet: [photoUrls]` skips `override` for `photoUrls` in schema `Pet`| |empty map| diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache index 512f79fc5fd4..5b9f530282d0 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache @@ -29,7 +29,11 @@ import org.springframework.http.ResponseEntity {{/useResponseEntity}} import org.springframework.web.bind.annotation.* -{{#useBeanValidation}}{{^useSpringBuiltInValidation}}import org.springframework.validation.annotation.Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} +{{#useBeanValidation}} +{{^useSpringBuiltInValidation}} +import org.springframework.validation.annotation.Validated +{{/useSpringBuiltInValidation}} +{{/useBeanValidation}} import org.springframework.web.context.request.NativeWebRequest import org.springframework.beans.factory.annotation.Autowired @@ -52,7 +56,11 @@ import kotlin.collections.List import kotlin.collections.Map @RestController{{#beanQualifiers}}("{{package}}.{{classname}}Controller"){{/beanQualifiers}} -{{#useBeanValidation}}{{^useSpringBuiltInValidation}}@Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} +{{#useBeanValidation}} +{{^useSpringBuiltInValidation}} +@Validated +{{/useSpringBuiltInValidation}} +{{/useBeanValidation}} {{#swagger1AnnotationLibrary}} @Api(value = "{{{baseName}}}", description = "The {{{baseName}}} API") {{/swagger1AnnotationLibrary}} diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache index cdac82dbeda3..82e091bd1aca 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache @@ -34,7 +34,11 @@ import org.springframework.http.ResponseEntity {{/useResponseEntity}} import org.springframework.web.bind.annotation.* -{{#useBeanValidation}}{{^useSpringBuiltInValidation}}import org.springframework.validation.annotation.Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} +{{#useBeanValidation}} +{{^useSpringBuiltInValidation}} +import org.springframework.validation.annotation.Validated +{{/useSpringBuiltInValidation}} +{{/useBeanValidation}} import org.springframework.web.context.request.NativeWebRequest import org.springframework.beans.factory.annotation.Autowired @@ -59,7 +63,11 @@ import kotlin.collections.Map {{^useFeignClient}} @RestController {{/useFeignClient}} -{{#useBeanValidation}}{{^useSpringBuiltInValidation}}@Validated{{/useSpringBuiltInValidation}}{{/useBeanValidation}} +{{#useBeanValidation}} +{{^useSpringBuiltInValidation}} +@Validated +{{/useSpringBuiltInValidation}} +{{/useBeanValidation}} {{#swagger1AnnotationLibrary}} @Api(value = "{{{baseName}}}", description = "The {{{baseName}}} API") {{/swagger1AnnotationLibrary}} From bb4112b41d8b488cfbb77129039a1aef42640ff9 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Wed, 24 Jun 2026 16:46:56 +0800 Subject: [PATCH 3/3] fix --- .../codegen/languages/KotlinSpringServerCodegen.java | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java index 69e759436809..619d44868dc4 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java @@ -532,6 +532,7 @@ public void processOpts() { } if (additionalProperties.containsKey(USE_SPRING_BUILT_IN_VALIDATION)) { this.setUseSpringBuiltInValidation(convertPropertyToBoolean(USE_SPRING_BUILT_IN_VALIDATION)); + writePropertyBack(USE_SPRING_BUILT_IN_VALIDATION, useSpringBuiltInValidation); } if (additionalProperties.containsKey(INCLUDE_HTTP_REQUEST_CONTEXT)) { this.setIncludeHttpRequestContext(convertPropertyToBoolean(INCLUDE_HTTP_REQUEST_CONTEXT));