spring-projects / spring-projects/spring-ai

Kotlin `entity()` extension crashes with NullPointerException instead of returning null

Open Beginner friendly
#6,826 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
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

  1. Call chatClient.prompt().user(...).call().entity<T>() from Kotlin.
  2. 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 returns null per its @Nullable contract.
  3. Observe that instead of getting null back, 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-29
    inline fun <reified T : Any> ChatClient.CallResponseSpec.entity(): T =
        entity(object : ParameterizedTypeReference<T>() {}) as T
    
  • spring-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 returns null (no parsable text content in the response).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.