From c79ab9b71a7e0ec4e8f0b72fbc8d5b32c98d1cfd Mon Sep 17 00:00:00 2001 From: Tolga Akkiraz <33528315+takkiraz@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:06:28 +0200 Subject: [PATCH] [kotlin][jvm-spring-restclient] make nullableReturnType type bound conditional in ApiClient Fixes #23654 PR #23613 unconditionally relaxed the type bound from `T : Any` to `T : Any?` in ApiClient.kt.mustache for jvm-spring-restclient. This breaks compilation with Spring Boot 4 / Spring Framework 7, where stricter nullability handling rejects nullable bounds on RestClient.ResponseSpec.toEntity(). Make the relaxation conditional on the nullableReturnType option: - Default (nullableReturnType=false): T : Any (identical to pre-#23613) - nullableReturnType=true: T : Any? (preserves the #23613 NPE fix) Also restores I bound to Any everywhere (request bodies are never nullable) and removes the unnecessary `as Any` cast in nullableBody. --- .../infrastructure/ApiClient.kt.mustache | 8 ++++---- .../org/openapitools/client/infrastructure/ApiClient.kt | 8 ++++---- .../org/openapitools/client/infrastructure/ApiClient.kt | 8 ++++---- .../org/openapitools/client/infrastructure/ApiClient.kt | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/infrastructure/ApiClient.kt.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/infrastructure/ApiClient.kt.mustache index 3612e97011dc..a95b714ec6ba 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/infrastructure/ApiClient.kt.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/infrastructure/ApiClient.kt.mustache @@ -10,13 +10,13 @@ import org.springframework.util.LinkedMultiValueMap {{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}open class ApiClient(protected val client: RestClient) { - protected inline fun request(requestConfig: RequestConfig): ResponseEntity { + protected inline fun request(requestConfig: RequestConfig): ResponseEntity { return prepare(defaults(requestConfig)) .retrieve() .toEntity(object : ParameterizedTypeReference() {}) } - protected fun prepare(requestConfig: RequestConfig) = + protected fun prepare(requestConfig: RequestConfig) = client.method(requestConfig) .uri(requestConfig) .headers(requestConfig) @@ -45,7 +45,7 @@ import org.springframework.util.LinkedMultiValueMap private fun RestClient.RequestBodySpec.headers(requestConfig: RequestConfig) = apply { requestConfig.headers.forEach { (name, value) -> header(name, value) } } - private fun RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig): RestClient.RequestBodySpec { + private fun RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig): RestClient.RequestBodySpec { when { requestConfig.headers[HttpHeaders.CONTENT_TYPE] == MediaType.MULTIPART_FORM_DATA_VALUE -> { val parts = LinkedMultiValueMap() @@ -59,7 +59,7 @@ import org.springframework.util.LinkedMultiValueMap } else -> { - return apply { if (requestConfig.body != null) body(requestConfig.body as Any) } + return apply { if (requestConfig.body != null) body(requestConfig.body) } } } } diff --git a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 0f8f3a7052d0..d13aa82aab69 100644 --- a/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,13 +10,13 @@ import org.springframework.util.LinkedMultiValueMap open class ApiClient(protected val client: RestClient) { - protected inline fun request(requestConfig: RequestConfig): ResponseEntity { + protected inline fun request(requestConfig: RequestConfig): ResponseEntity { return prepare(defaults(requestConfig)) .retrieve() .toEntity(object : ParameterizedTypeReference() {}) } - protected fun prepare(requestConfig: RequestConfig) = + protected fun prepare(requestConfig: RequestConfig) = client.method(requestConfig) .uri(requestConfig) .headers(requestConfig) @@ -45,7 +45,7 @@ open class ApiClient(protected val client: RestClient) { private fun RestClient.RequestBodySpec.headers(requestConfig: RequestConfig) = apply { requestConfig.headers.forEach { (name, value) -> header(name, value) } } - private fun RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig): RestClient.RequestBodySpec { + private fun RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig): RestClient.RequestBodySpec { when { requestConfig.headers[HttpHeaders.CONTENT_TYPE] == MediaType.MULTIPART_FORM_DATA_VALUE -> { val parts = LinkedMultiValueMap() @@ -59,7 +59,7 @@ open class ApiClient(protected val client: RestClient) { } else -> { - return apply { if (requestConfig.body != null) body(requestConfig.body as Any) } + return apply { if (requestConfig.body != null) body(requestConfig.body) } } } } diff --git a/samples/client/others/kotlin-jvm-spring-3-restclient-nullable-return/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/others/kotlin-jvm-spring-3-restclient-nullable-return/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 0f8f3a7052d0..b1675a9ff649 100644 --- a/samples/client/others/kotlin-jvm-spring-3-restclient-nullable-return/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/others/kotlin-jvm-spring-3-restclient-nullable-return/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,13 +10,13 @@ import org.springframework.util.LinkedMultiValueMap open class ApiClient(protected val client: RestClient) { - protected inline fun request(requestConfig: RequestConfig): ResponseEntity { + protected inline fun request(requestConfig: RequestConfig): ResponseEntity { return prepare(defaults(requestConfig)) .retrieve() .toEntity(object : ParameterizedTypeReference() {}) } - protected fun prepare(requestConfig: RequestConfig) = + protected fun prepare(requestConfig: RequestConfig) = client.method(requestConfig) .uri(requestConfig) .headers(requestConfig) @@ -45,7 +45,7 @@ open class ApiClient(protected val client: RestClient) { private fun RestClient.RequestBodySpec.headers(requestConfig: RequestConfig) = apply { requestConfig.headers.forEach { (name, value) -> header(name, value) } } - private fun RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig): RestClient.RequestBodySpec { + private fun RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig): RestClient.RequestBodySpec { when { requestConfig.headers[HttpHeaders.CONTENT_TYPE] == MediaType.MULTIPART_FORM_DATA_VALUE -> { val parts = LinkedMultiValueMap() @@ -59,7 +59,7 @@ open class ApiClient(protected val client: RestClient) { } else -> { - return apply { if (requestConfig.body != null) body(requestConfig.body as Any) } + return apply { if (requestConfig.body != null) body(requestConfig.body) } } } } diff --git a/samples/client/petstore/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt index 0f8f3a7052d0..d13aa82aab69 100644 --- a/samples/client/petstore/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt @@ -10,13 +10,13 @@ import org.springframework.util.LinkedMultiValueMap open class ApiClient(protected val client: RestClient) { - protected inline fun request(requestConfig: RequestConfig): ResponseEntity { + protected inline fun request(requestConfig: RequestConfig): ResponseEntity { return prepare(defaults(requestConfig)) .retrieve() .toEntity(object : ParameterizedTypeReference() {}) } - protected fun prepare(requestConfig: RequestConfig) = + protected fun prepare(requestConfig: RequestConfig) = client.method(requestConfig) .uri(requestConfig) .headers(requestConfig) @@ -45,7 +45,7 @@ open class ApiClient(protected val client: RestClient) { private fun RestClient.RequestBodySpec.headers(requestConfig: RequestConfig) = apply { requestConfig.headers.forEach { (name, value) -> header(name, value) } } - private fun RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig): RestClient.RequestBodySpec { + private fun RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig): RestClient.RequestBodySpec { when { requestConfig.headers[HttpHeaders.CONTENT_TYPE] == MediaType.MULTIPART_FORM_DATA_VALUE -> { val parts = LinkedMultiValueMap() @@ -59,7 +59,7 @@ open class ApiClient(protected val client: RestClient) { } else -> { - return apply { if (requestConfig.body != null) body(requestConfig.body as Any) } + return apply { if (requestConfig.body != null) body(requestConfig.body) } } } }