Skip to content

Commit 1e8ef54

Browse files
authored
[kotlin][jvm-spring-restclient] make nullableReturnType type bound conditional in ApiClient (#23657)
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.
1 parent 9faa37f commit 1e8ef54

4 files changed

Lines changed: 16 additions & 16 deletions

File tree

  • modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/infrastructure
  • samples/client
    • echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure
    • others/kotlin-jvm-spring-3-restclient-nullable-return/src/main/kotlin/org/openapitools/client/infrastructure
    • petstore/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure

modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/infrastructure/ApiClient.kt.mustache

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import org.springframework.util.LinkedMultiValueMap
1010

1111
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}open class ApiClient(protected val client: RestClient) {
1212
13-
protected inline fun <reified I : Any?, reified T: Any?> request(requestConfig: RequestConfig<I>): ResponseEntity<T> {
13+
protected inline fun <reified I : Any, reified T: Any{{#nullableReturnType}}?{{/nullableReturnType}}> request(requestConfig: RequestConfig<I>): ResponseEntity<T> {
1414
return prepare(defaults(requestConfig))
1515
.retrieve()
1616
.toEntity(object : ParameterizedTypeReference<T>() {})
1717
}
1818

19-
protected fun <I : Any?> prepare(requestConfig: RequestConfig<I>) =
19+
protected fun <I : Any> prepare(requestConfig: RequestConfig<I>) =
2020
client.method(requestConfig)
2121
.uri(requestConfig)
2222
.headers(requestConfig)
@@ -45,7 +45,7 @@ import org.springframework.util.LinkedMultiValueMap
4545
private fun <I> RestClient.RequestBodySpec.headers(requestConfig: RequestConfig<I>) =
4646
apply { requestConfig.headers.forEach { (name, value) -> header(name, value) } }
4747

48-
private fun <I : Any?> RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig<I>): RestClient.RequestBodySpec {
48+
private fun <I : Any> RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig<I>): RestClient.RequestBodySpec {
4949
when {
5050
requestConfig.headers[HttpHeaders.CONTENT_TYPE] == MediaType.MULTIPART_FORM_DATA_VALUE -> {
5151
val parts = LinkedMultiValueMap<String, Any>()
@@ -59,7 +59,7 @@ import org.springframework.util.LinkedMultiValueMap
5959
}
6060

6161
else -> {
62-
return apply { if (requestConfig.body != null) body(requestConfig.body as Any) }
62+
return apply { if (requestConfig.body != null) body(requestConfig.body) }
6363
}
6464
}
6565
}

samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import org.springframework.util.LinkedMultiValueMap
1010

1111
open class ApiClient(protected val client: RestClient) {
1212

13-
protected inline fun <reified I : Any?, reified T: Any?> request(requestConfig: RequestConfig<I>): ResponseEntity<T> {
13+
protected inline fun <reified I : Any, reified T: Any> request(requestConfig: RequestConfig<I>): ResponseEntity<T> {
1414
return prepare(defaults(requestConfig))
1515
.retrieve()
1616
.toEntity(object : ParameterizedTypeReference<T>() {})
1717
}
1818

19-
protected fun <I : Any?> prepare(requestConfig: RequestConfig<I>) =
19+
protected fun <I : Any> prepare(requestConfig: RequestConfig<I>) =
2020
client.method(requestConfig)
2121
.uri(requestConfig)
2222
.headers(requestConfig)
@@ -45,7 +45,7 @@ open class ApiClient(protected val client: RestClient) {
4545
private fun <I> RestClient.RequestBodySpec.headers(requestConfig: RequestConfig<I>) =
4646
apply { requestConfig.headers.forEach { (name, value) -> header(name, value) } }
4747

48-
private fun <I : Any?> RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig<I>): RestClient.RequestBodySpec {
48+
private fun <I : Any> RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig<I>): RestClient.RequestBodySpec {
4949
when {
5050
requestConfig.headers[HttpHeaders.CONTENT_TYPE] == MediaType.MULTIPART_FORM_DATA_VALUE -> {
5151
val parts = LinkedMultiValueMap<String, Any>()
@@ -59,7 +59,7 @@ open class ApiClient(protected val client: RestClient) {
5959
}
6060

6161
else -> {
62-
return apply { if (requestConfig.body != null) body(requestConfig.body as Any) }
62+
return apply { if (requestConfig.body != null) body(requestConfig.body) }
6363
}
6464
}
6565
}

samples/client/others/kotlin-jvm-spring-3-restclient-nullable-return/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import org.springframework.util.LinkedMultiValueMap
1010

1111
open class ApiClient(protected val client: RestClient) {
1212

13-
protected inline fun <reified I : Any?, reified T: Any?> request(requestConfig: RequestConfig<I>): ResponseEntity<T> {
13+
protected inline fun <reified I : Any, reified T: Any?> request(requestConfig: RequestConfig<I>): ResponseEntity<T> {
1414
return prepare(defaults(requestConfig))
1515
.retrieve()
1616
.toEntity(object : ParameterizedTypeReference<T>() {})
1717
}
1818

19-
protected fun <I : Any?> prepare(requestConfig: RequestConfig<I>) =
19+
protected fun <I : Any> prepare(requestConfig: RequestConfig<I>) =
2020
client.method(requestConfig)
2121
.uri(requestConfig)
2222
.headers(requestConfig)
@@ -45,7 +45,7 @@ open class ApiClient(protected val client: RestClient) {
4545
private fun <I> RestClient.RequestBodySpec.headers(requestConfig: RequestConfig<I>) =
4646
apply { requestConfig.headers.forEach { (name, value) -> header(name, value) } }
4747

48-
private fun <I : Any?> RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig<I>): RestClient.RequestBodySpec {
48+
private fun <I : Any> RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig<I>): RestClient.RequestBodySpec {
4949
when {
5050
requestConfig.headers[HttpHeaders.CONTENT_TYPE] == MediaType.MULTIPART_FORM_DATA_VALUE -> {
5151
val parts = LinkedMultiValueMap<String, Any>()
@@ -59,7 +59,7 @@ open class ApiClient(protected val client: RestClient) {
5959
}
6060

6161
else -> {
62-
return apply { if (requestConfig.body != null) body(requestConfig.body as Any) }
62+
return apply { if (requestConfig.body != null) body(requestConfig.body) }
6363
}
6464
}
6565
}

samples/client/petstore/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import org.springframework.util.LinkedMultiValueMap
1010

1111
open class ApiClient(protected val client: RestClient) {
1212

13-
protected inline fun <reified I : Any?, reified T: Any?> request(requestConfig: RequestConfig<I>): ResponseEntity<T> {
13+
protected inline fun <reified I : Any, reified T: Any> request(requestConfig: RequestConfig<I>): ResponseEntity<T> {
1414
return prepare(defaults(requestConfig))
1515
.retrieve()
1616
.toEntity(object : ParameterizedTypeReference<T>() {})
1717
}
1818

19-
protected fun <I : Any?> prepare(requestConfig: RequestConfig<I>) =
19+
protected fun <I : Any> prepare(requestConfig: RequestConfig<I>) =
2020
client.method(requestConfig)
2121
.uri(requestConfig)
2222
.headers(requestConfig)
@@ -45,7 +45,7 @@ open class ApiClient(protected val client: RestClient) {
4545
private fun <I> RestClient.RequestBodySpec.headers(requestConfig: RequestConfig<I>) =
4646
apply { requestConfig.headers.forEach { (name, value) -> header(name, value) } }
4747

48-
private fun <I : Any?> RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig<I>): RestClient.RequestBodySpec {
48+
private fun <I : Any> RestClient.RequestBodySpec.nullableBody(requestConfig: RequestConfig<I>): RestClient.RequestBodySpec {
4949
when {
5050
requestConfig.headers[HttpHeaders.CONTENT_TYPE] == MediaType.MULTIPART_FORM_DATA_VALUE -> {
5151
val parts = LinkedMultiValueMap<String, Any>()
@@ -59,7 +59,7 @@ open class ApiClient(protected val client: RestClient) {
5959
}
6060

6161
else -> {
62-
return apply { if (requestConfig.body != null) body(requestConfig.body as Any) }
62+
return apply { if (requestConfig.body != null) body(requestConfig.body) }
6363
}
6464
}
6565
}

0 commit comments

Comments
 (0)