diff --git a/docs/generators/kotlin-spring.md b/docs/generators/kotlin-spring.md
index e4a0074ee562..7210790a52a9 100644
--- a/docs/generators/kotlin-spring.md
+++ b/docs/generators/kotlin-spring.md
@@ -33,6 +33,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|documentationProvider|Select the OpenAPI documentation provider.|
- **none**
- Do not publish an OpenAPI specification.
- **source**
- Publish the original input OpenAPI specification.
- **springdoc**
- Generate an OpenAPI 3 specification using SpringDoc.
|springdoc|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|exceptionHandler|generate default global exception handlers (not compatible with reactive. enabling reactive will disable exceptionHandler )| |true|
+|generateGenericResponseEntity|Use a generic type for the ResponseEntity wrapping return values of generated API methods. If enabled, methods are generated with return type ResponseEntity<*>| |false|
|generateJsonIncludeAnnotations|Whether to generate policy @JsonInclude annotations on model properties. When true, emits spec-honest annotations (required-field protection and the optional non-nullable policy from optionalNonNullPropertyJsonInclude). When false, none are generated and the global ObjectMapper owns inclusion. When left unset it defaults to false (7.23.0-equivalent output) and logs a warning; set it explicitly to silence the warning. A per-property override set via the `x-jackson-json-include-policy` vendor extension is always honored regardless of this flag.| |false|
|generateJsonSetterNullsAnnotations|Whether to generate @JsonSetter(nulls = ...) annotations on optional non-nullable model properties. When true, emits @JsonSetter (Nulls.FAIL when openApiNullable is true, otherwise Nulls.SKIP) so an explicit null in the payload is handled explicitly. When false, none are generated and deserialization null-handling defers to the global ObjectMapper. When left unset it defaults to false (7.23.0-equivalent output) and logs a warning; set it explicitly to silence the warning.| |false|
|generatePageableConstraintValidation|Generate a @ValidPageable annotation and PageableConstraintValidator class, and apply @ValidPageable to the injected Pageable parameter of operations whose 'page' or 'size' parameter specifies a maximum constraint. The annotation enforces those constraints on the Pageable object that replaces the individual page/size query parameters. Requires useBeanValidation=true and library is spring-boot or spring-cloud.| |false|
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 12047a88e12f..a4465496fa3f 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
@@ -100,6 +100,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String USE_TAGS = "useTags";
public static final String BEAN_QUALIFIERS = "beanQualifiers";
public static final String USE_RESPONSE_ENTITY = "useResponseEntity";
+ public static final String GENERATE_GENERIC_RESPONSE_ENTITY = "generateGenericResponseEntity";
public static final String DECLARATIVE_INTERFACE_REACTIVE_MODE = "declarativeInterfaceReactiveMode";
public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
@@ -176,6 +177,7 @@ public enum RequestMappingMode {
@Setter private boolean beanQualifiers = false;
@Setter private DeclarativeInterfaceReactiveMode declarativeInterfaceReactiveMode = DeclarativeInterfaceReactiveMode.coroutines;
@Setter private boolean useResponseEntity = true;
+ @Setter private boolean generateGenericResponseEntity = false;
@Setter private boolean autoXSpringPaginated = false;
@Setter private boolean generateSortValidation = false;
@Setter private boolean generatePageableConstraintValidation = false;
@@ -304,6 +306,9 @@ public KotlinSpringServerCodegen() {
addSwitch(USE_RESPONSE_ENTITY,
"Whether (when false) to return actual type (e.g. List) and handle non-happy path responses via exceptions flow or (when true) return entire ResponseEntity (e.g. ResponseEntity>). If disabled, method are annotated using a @ResponseStatus annotation, which has the status of the first response declared in the Api definition",
useResponseEntity);
+ addSwitch(GENERATE_GENERIC_RESPONSE_ENTITY,
+ "Use a generic type for the ResponseEntity wrapping return values of generated API methods. If enabled, methods are generated with return type ResponseEntity<*>",
+ generateGenericResponseEntity);
addSwitch(USE_SEALED_RESPONSE_INTERFACES,
"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)",
useSealedResponseInterfaces);
@@ -613,11 +618,19 @@ public void processOpts() {
additionalProperties.put(CodegenConstants.LIBRARY, library);
}
- if(additionalProperties.containsKey(USE_RESPONSE_ENTITY)) {
+ if (additionalProperties.containsKey(USE_RESPONSE_ENTITY)) {
this.setUseResponseEntity(Boolean.parseBoolean(additionalProperties.get(USE_RESPONSE_ENTITY).toString()));
}
writePropertyBack(USE_RESPONSE_ENTITY, useResponseEntity);
+ if (additionalProperties.containsKey(GENERATE_GENERIC_RESPONSE_ENTITY)) {
+ this.setGenerateGenericResponseEntity(Boolean.parseBoolean(additionalProperties.get(GENERATE_GENERIC_RESPONSE_ENTITY).toString()));
+ }
+ if (!useResponseEntity) {
+ this.setGenerateGenericResponseEntity(false);
+ }
+ writePropertyBack(GENERATE_GENERIC_RESPONSE_ENTITY, generateGenericResponseEntity);
+
if(additionalProperties.containsKey(USE_SEALED_RESPONSE_INTERFACES)) {
this.setUseSealedResponseInterfaces(Boolean.parseBoolean(additionalProperties.get(USE_SEALED_RESPONSE_INTERFACES).toString()));
}
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 9ff4eb9308dc..177cc0337d91 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache
@@ -120,7 +120,7 @@ class {{classname}}Controller({{#serviceInterface}}@Autowired(required = true) v
{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
{{/includeHttpRequestContext}}{{/hasParams}}{{#vendorExtensions.x-pageable-extra-annotation}}{{{.}}} {{/vendorExtensions.x-pageable-extra-annotation}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}{{#hasParams}}
- {{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}} {
+ {{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{>returnTypes}}{{/generateGenericResponseEntity}}>{{/useResponseEntity}}{{^useResponseEntity}}{{>returnTypes}}{{/useResponseEntity}} {
return {{>returnValue}}
}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache
index e6efd9948039..b7b931e8b3de 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache
@@ -36,7 +36,7 @@ interface {{classname}}Delegate {
{{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}},
{{/hasParams}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
- {{/includeHttpRequestContext}}{{/hasParams}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultDelegateInterface}} {
+ {{/includeHttpRequestContext}}{{/hasParams}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}): {{#useResponseEntity}}ResponseEntity<{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{>returnTypes}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{>returnTypes}}{{/useSealedResponseInterfaces}}{{/generateGenericResponseEntity}}>{{/useResponseEntity}}{{^useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{>returnTypes}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{>returnTypes}}{{/useSealedResponseInterfaces}}{{/useResponseEntity}}{{^skipDefaultDelegateInterface}} {
{{>methodBody}}{{! prevent indent}}
}{{/skipDefaultDelegateInterface}}
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 8059e27f83b6..132f78475c9c 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache
@@ -135,7 +135,7 @@ interface {{classname}} {
{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
{{/includeHttpRequestContext}}{{/hasParams}}{{#vendorExtensions.x-pageable-extra-annotation}}{{{.}}} {{/vendorExtensions.x-pageable-extra-annotation}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}{{#hasParams}}
- {{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{>returnTypes}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{>returnTypes}}{{/useSealedResponseInterfaces}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultApiInterface}} {
+ {{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{>returnTypes}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{>returnTypes}}{{/useSealedResponseInterfaces}}{{/generateGenericResponseEntity}}>{{/useResponseEntity}}{{^useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{>returnTypes}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{>returnTypes}}{{/useSealedResponseInterfaces}}{{/useResponseEntity}}{{^skipDefaultApiInterface}} {
{{^isDelegate}}
return {{>returnValue}}
{{/isDelegate}}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/api_test.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/api_test.mustache
index 8950633ae979..83d92f25e755 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-spring/api_test.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-spring/api_test.mustache
@@ -36,7 +36,7 @@ class {{classname}}Test {
{{/allParams}}
{{#includeHttpRequestContext}}val {{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}} = TODO(){{/includeHttpRequestContext}}
{{#vendorExtensions.x-spring-paginated}}val pageable: Pageable = TODO(){{/vendorExtensions.x-spring-paginated}}
- val response: {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}} = api.{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}request{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}})
+ val response: {{#useResponseEntity}}ResponseEntity<{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{>returnTypes}}{{/generateGenericResponseEntity}}>{{/useResponseEntity}}{{^useResponseEntity}}{{>returnTypes}}{{/useResponseEntity}} = api.{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}request{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}})
// TODO: test validations
}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache
index f4bb3da4fa13..a9ce4486e91e 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache
@@ -1,26 +1,26 @@
{{! handle reactive map and array}}
{{#reactive}}
{{#isMap}}
-{{#reactiveModeReactor}}Mono<{{/reactiveModeReactor}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}Map{{#useResponseEntity}}>{{/useResponseEntity}}{{#reactiveModeReactor}}>{{/reactiveModeReactor}}
+{{#reactiveModeReactor}}Mono<{{/reactiveModeReactor}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}Map{{/generateGenericResponseEntity}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#reactiveModeReactor}}>{{/reactiveModeReactor}}
{{/isMap}}
{{#isArray}}
{{! array handle reactive - reactor with/without ResponseEntity wrapper}}
{{#reactiveModeReactor}}
-Mono<{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{#useResponseEntity}}>{{/useResponseEntity}}>
+Mono<{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{/generateGenericResponseEntity}}{{#useResponseEntity}}>{{/useResponseEntity}}>
{{/reactiveModeReactor}}
{{! array handle reactive - coroutines with/without ResponseEntity wrapper}}
{{#reactiveModeCoroutines}}
-{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{#useResponseEntity}}>{{/useResponseEntity}}
+{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{/generateGenericResponseEntity}}{{#useResponseEntity}}>{{/useResponseEntity}}
{{/reactiveModeCoroutines}}
{{/isArray}}
{{! handle reactive non-container - with/without ResponseEntity wrapper}}
{{^returnContainer}}
{{#reactiveModeReactor}}
-Mono<{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{#useResponseEntity}}>{{/useResponseEntity}}>
+Mono<{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{/generateGenericResponseEntity}}{{#useResponseEntity}}>{{/useResponseEntity}}>
{{/reactiveModeReactor}}
{{#reactiveModeCoroutines}}
{{#useResponseEntity}}
-ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{#useResponseEntity}}>
+ResponseEntity<{{/useResponseEntity}}{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{/generateGenericResponseEntity}}{{#useResponseEntity}}>
{{/useResponseEntity}}
{{/reactiveModeCoroutines}}
{{/returnContainer}}
@@ -29,19 +29,19 @@ ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorEx
{{! handle non-reactive map and array}}
{{#isMap}}
{{#useResponseEntity}}
-ResponseEntity<{{/useResponseEntity}}Map{{#useResponseEntity}}>
+ResponseEntity<{{/useResponseEntity}}{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}Map{{/generateGenericResponseEntity}}{{#useResponseEntity}}>
{{/useResponseEntity}}
{{/isMap}}
{{#isArray}}
{{! array handle non-reactive - with/without ResponseEntity wrapper}}
{{#useResponseEntity}}
-ResponseEntity<{{/useResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{#useResponseEntity}}>
+ResponseEntity<{{/useResponseEntity}}{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{/generateGenericResponseEntity}}{{#useResponseEntity}}>
{{/useResponseEntity}}
{{/isArray}}
{{! handle reactive non-container - with/without ResponseEntity wrapper}}
{{^returnContainer}}
{{#useResponseEntity}}
-ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{#useResponseEntity}}>
+ResponseEntity<{{/useResponseEntity}}{{#generateGenericResponseEntity}}*{{/generateGenericResponseEntity}}{{^generateGenericResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}{{/generateGenericResponseEntity}}{{#useResponseEntity}}>
{{/useResponseEntity}}
{{/returnContainer}}
-{{/reactive}}
\ No newline at end of file
+{{/reactive}}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java
index 7671d2683121..31f71ca0ff94 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java
@@ -3202,6 +3202,139 @@ public void nonReactiveWithResponseEntity() throws Exception {
);
}
+ @Test
+ public void genericResponseEntityForMultipleResponseShapes() throws Exception {
+ Map additionalProperties = new HashMap<>();
+ additionalProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, true);
+ additionalProperties.put(KotlinSpringServerCodegen.GENERATE_GENERIC_RESPONSE_ENTITY, true);
+
+ Map files = generateFromContract(
+ "src/test/resources/3_0/kotlin/issue_24804.yaml", additionalProperties);
+
+ assertFileContains(files.get("OperationsApiController.kt").toPath(),
+ "fun startOperation(): ResponseEntity<*>");
+ assertFileContains(files.get("OperationsApiTest.kt").toPath(),
+ "val response: ResponseEntity<*> = api.startOperation()");
+ }
+
+ @Test
+ public void genericResponseEntityForInterfaceAndDelegate() throws Exception {
+ Map interfaceProperties = new HashMap<>();
+ interfaceProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, true);
+ interfaceProperties.put(KotlinSpringServerCodegen.GENERATE_GENERIC_RESPONSE_ENTITY, true);
+ interfaceProperties.put(INTERFACE_ONLY, true);
+
+ Map interfaceFiles = generateFromContract(
+ "src/test/resources/3_0/kotlin/issue_24804.yaml", interfaceProperties);
+ assertFileContains(interfaceFiles.get("OperationsApi.kt").toPath(),
+ "fun startOperation(): ResponseEntity<*>");
+
+ Map delegateProperties = new HashMap<>();
+ delegateProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, true);
+ delegateProperties.put(KotlinSpringServerCodegen.GENERATE_GENERIC_RESPONSE_ENTITY, true);
+ delegateProperties.put(DELEGATE_PATTERN, true);
+
+ Map delegateFiles = generateFromContract(
+ "src/test/resources/3_0/kotlin/issue_24804.yaml", delegateProperties);
+ assertFileContains(delegateFiles.get("OperationsApi.kt").toPath(),
+ "fun startOperation(): ResponseEntity<*>");
+ assertFileContains(delegateFiles.get("OperationsApiDelegate.kt").toPath(),
+ "fun startOperation(): ResponseEntity<*>");
+ }
+
+ @Test
+ public void genericResponseEntityIsIgnoredWithoutResponseEntity() throws Exception {
+ Map additionalProperties = new HashMap<>();
+ additionalProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, false);
+ additionalProperties.put(KotlinSpringServerCodegen.GENERATE_GENERIC_RESPONSE_ENTITY, true);
+
+ Map files = generateFromContract(
+ "src/test/resources/3_0/kotlin/issue_24804.yaml", additionalProperties);
+
+ assertFileContains(files.get("OperationsApiController.kt").toPath(),
+ "fun startOperation(): Unit");
+ assertFileNotContains(files.get("OperationsApiController.kt").toPath(), "ResponseEntity<*>");
+ }
+
+ @Test
+ public void genericResponseEntityForDeclarativeHttpInterface() throws Exception {
+ Map additionalProperties = new HashMap<>();
+ additionalProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, true);
+ additionalProperties.put(KotlinSpringServerCodegen.GENERATE_GENERIC_RESPONSE_ENTITY, true);
+ additionalProperties.put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, false);
+
+ Map files = generateFromContract(
+ "src/test/resources/3_0/kotlin/issue_24804.yaml",
+ additionalProperties,
+ new HashMap<>(),
+ configurator -> configurator.setLibrary(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY));
+
+ assertFileContains(files.get("DefaultApi.kt").toPath(),
+ "fun startOperation(",
+ "): ResponseEntity<*>");
+ }
+
+ @Test
+ public void sealedResponseInterfaceIsPreservedWithoutResponseEntity() throws Exception {
+ Map additionalProperties = new HashMap<>();
+ additionalProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, false);
+ additionalProperties.put(KotlinSpringServerCodegen.USE_SEALED_RESPONSE_INTERFACES, true);
+ additionalProperties.put(INTERFACE_ONLY, true);
+
+ Map files = generateFromContract(
+ "src/test/resources/3_0/kotlin/sealed-response-interfaces.yaml", additionalProperties);
+
+ assertFileContains(files.get("UsersApi.kt").toPath(),
+ "): CreateUserResponse",
+ "): GetUserResponse");
+ }
+
+ @Test
+ public void sealedResponseInterfaceIsPreservedInDelegateWithoutResponseEntity() throws Exception {
+ Map additionalProperties = new HashMap<>();
+ additionalProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, false);
+ additionalProperties.put(KotlinSpringServerCodegen.USE_SEALED_RESPONSE_INTERFACES, true);
+ additionalProperties.put(DELEGATE_PATTERN, true);
+
+ Map files = generateFromContract(
+ "src/test/resources/3_0/kotlin/sealed-response-interfaces.yaml", additionalProperties);
+
+ assertFileContains(files.get("UsersApiDelegate.kt").toPath(),
+ "): CreateUserResponse",
+ "): GetUserResponse");
+ }
+
+ @Test
+ public void sealedResponseInterfaceDoesNotChangeControllerServiceReturnType() throws Exception {
+ Map additionalProperties = new HashMap<>();
+ additionalProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, true);
+ additionalProperties.put(KotlinSpringServerCodegen.USE_SEALED_RESPONSE_INTERFACES, true);
+ additionalProperties.put(SERVICE_INTERFACE, true);
+
+ Map files = generateFromContract(
+ "src/test/resources/3_0/kotlin/sealed-response-interfaces.yaml", additionalProperties);
+
+ assertFileContains(files.get("UsersApiController.kt").toPath(),
+ "): ResponseEntity");
+ assertFileContains(files.get("UsersApiService.kt").toPath(),
+ "): User");
+ }
+
+ @Test
+ public void sealedResponseInterfaceIsPreservedInDelegate() throws Exception {
+ Map additionalProperties = new HashMap<>();
+ additionalProperties.put(KotlinSpringServerCodegen.USE_RESPONSE_ENTITY, true);
+ additionalProperties.put(KotlinSpringServerCodegen.USE_SEALED_RESPONSE_INTERFACES, true);
+ additionalProperties.put(DELEGATE_PATTERN, true);
+
+ Map files = generateFromContract(
+ "src/test/resources/3_0/kotlin/sealed-response-interfaces.yaml", additionalProperties);
+
+ assertFileContains(files.get("UsersApiDelegate.kt").toPath(),
+ "): ResponseEntity",
+ "): ResponseEntity");
+ }
+
/**
* Regression test for https://github.com/OpenAPITools/openapi-generator/issues/17445.
* OpenAPI 'default' responses must emit responseCode = "default" in @ApiResponse (swagger2),
diff --git a/modules/openapi-generator/src/test/resources/3_0/kotlin/issue_24804.yaml b/modules/openapi-generator/src/test/resources/3_0/kotlin/issue_24804.yaml
new file mode 100644
index 000000000000..6815e47503d2
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/kotlin/issue_24804.yaml
@@ -0,0 +1,26 @@
+openapi: 3.0.3
+info:
+ title: Generic response entity reproduction
+ version: 1.0.0
+paths:
+ /operations:
+ post:
+ operationId: startOperation
+ responses:
+ '201':
+ description: Operation completed successfully
+ '202':
+ description: Operation accepted for asynchronous processing
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/OperationStatusResponse'
+components:
+ schemas:
+ OperationStatusResponse:
+ type: object
+ required:
+ - status
+ properties:
+ status:
+ type: string