spring-projects / spring-projects/spring-ai
Kotlin `entity()` extension crashes with NullPointerException instead of returning null
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.5k
- Forks
- 2.9k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 6
Description
Kotlin entity() extension crashes with NullPointerException instead of returning null
Bug description
The Kotlin reified extension ChatClient.CallResponseSpec.entity(): T (in ChatClientExtensions.kt) force-casts the result of the underlying Java entity(ParameterizedTypeReference<T>) method with as T. That Java method is declared @Nullable T and legitimately returns null whenever the model response has no parsable text content (e.g. a tool-call-only turn, or an empty generation). When that happens, the Kotlin extension's as T cast throws an unhandled NullPointerException ("null cannot be cast to non-null type ...") instead of surfacing the documented nullable contract to the caller.
The same issue affects nothing else in the file - responseEntity() is already correctly typed and unaffected.
Environment
- Spring AI:
main(2.0.1-SNAPSHOT) - Kotlin: 2.3.20 (the version pinned in the root
pom.xml) - Java: 17
- Vector store: not applicable - this is in
spring-ai-client-chat, unrelated to any vector store
Steps to reproduce
- Call
chatClient.prompt().user(...).call().entity<T>()from Kotlin. - Have the model turn produce no parsable text content for the response (e.g. the assistant message only contains tool calls, or the generation text is empty) - the underlying Java
entity(ParameterizedTypeReference)method then legitimately returnsnullper its@Nullablecontract. - Observe that instead of getting
nullback, the call crashes.
Expected behavior
entity() should honor the @Nullable contract of the underlying Java API. When the model returns no parsable content, the Kotlin caller should either receive null (with a nullable return type) or a clear, documented exception, not an incidental NullPointerException raised by an implementation-detail cast.
Minimal Complete Reproducible example
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.ai.chat.client.ChatClient
import org.springframework.ai.chat.client.entity
import org.springframework.core.ParameterizedTypeReference
class EntityNullCastTest {
data class Joke(val setup: String, val punchline: String)
@Test
fun `entity() throws when the underlying Java API legitimately returns null`() {
val crs = mockk<ChatClient.CallResponseSpec>()
// Simulates the model producing no parsable content, which is when
// the underlying Java entity(ParameterizedTypeReference) method
// returns null per its @Nullable contract.
every { crs.entity(any<ParameterizedTypeReference<Joke>>()) } returns null
// Fails today with:
// java.lang.NullPointerException: null cannot be cast to non-null type Joke
assertThrows<NullPointerException> {
crs.entity<Joke>()
}
}
}
Relevant source:
spring-ai-client-chat/src/main/kotlin/org/springframework/ai/chat/client/ChatClientExtensions.kt:28-29inline fun <reified T : Any> ChatClient.CallResponseSpec.entity(): T = entity(object : ParameterizedTypeReference<T>() {}) as Tspring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClient.java:254<T> @Nullable T entity(ParameterizedTypeReference<T> type);spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java:610-616- shows the concrete condition under which the Java method returnsnull(no parsable text content in the response).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in spring-ai-client-chat/src/main/kotlin/org/springframework/ai/chat/client/ChatClientExtensions.kt at the reified entity() extension, then compare its cast with the @Nullable Java method in ChatClient.java and the null-return condition in DefaultChatClient.java. Run the Kotlin reproduction or a focused equivalent test; done means a null underlying response no longer produces an incidental NullPointerException and the nullable contract is covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, kotlin
- Domain
- api
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100