Chat Client APIs and Streaming
Chat Client APIs and Streaming
call(). content() (Most Common)
Returns only the generated text.
@GetMapping("/content")
public String content(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
Flow:
User │ ▼ ChatClient │ ▼ LLM │ ▼ String
Use when you only need the answer.
call().chatResponse()
Returns the complete response object, not just the text.
@GetMapping("/response")
public ChatResponse response(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.call()
.chatResponse();
}
Chat Response contains much more than the content.
http://localhost:8080/api/response?question=Explain Kafka
output are in json format .
Streaming
Instead of waiting for the complete answer, stream it token by token.
@GetMapping(value="/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.stream()
.content();
}
Now the browser receives:
data:Certainly data:! data: Here's data: an data: explanation
instead of waiting for the full response.Why "data:"?
SSE (Server-Sent Events) has a standard format.
One message looks like
data: KafkaNotice the blank line afterward.
Another message
data: isAnother
data: aThe browser understands this format automatically.
Streaming ChatResponse
Instead of streaming plain text:
Flux<String>
you can stream complete response objects.
@GetMapping(value="/stream-response",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponse> streamResponse(@RequestParam String question){
return chatClient.prompt()
.user(question)
.stream()
.chatResponse();
}Useful when you want metadata while streaming.
Mapping to a Java Object
Suppose you ask:
Give employee details.
Create a record:
public record Employee(
String name,
int age,
String city
) {}
Then:
Employee employee =
chatClient.prompt()
.user("Generate employee details")
.call()
.entity(Employee.class);
Spring AI asks the model for structured output and maps it into your Java type.
List Mapping
public record Employee(
String name,
int age
) {}
Then:
List<Employee> employees =
chatClient.prompt()
.user("Generate 5 employees")
.call()
.entity(new ParameterizedTypeReference<List<Employee>>() {});
Bean Mapping
Bean Mapping
public class Student {
private String name;
private int age;
// getters/setters
}
Then:
Student student =
chatClient.prompt()
.user("Generate student")
.call()
.entity(Student.class);
JavaScript Frontend (Production)
<!DOCTYPE html>
<html>
<body>
<button onclick="start()">Ask AI</button>
<pre id="output"></pre>
<script>
function start(){
const eventSource = new EventSource(
"http://localhost:8080/stream?question=Explain Kafka"
);
eventSource.onmessage = function(event){
document.getElementById("output").textContent += event.data;
};
}
</script>
</body>
</html>
What Happens Internally
User │ ▼ ChatClient │ ▼ OpenAI Streaming API │ ▼ Token 1 │ ▼ Flux<String> │ ▼ Spring WebFlux │ ▼ Browser / Postman / curl
Flux<String> is a Publisher from Project Reactor. Instead of waiting for the complete response, it emits each chunk as soon as it arrives, which is why streaming feels much more responsive in AI chat applications.
Production Flow
User │ ▼ ChatClient │ ▼ OpenAI Streaming API │ ▼ Chunk 1 → "Certainly" │ Chunk 2 → "!" │ Chunk 3 → "Here" │ Chunk 4 → "'s" │ Chunk 5 → "an" │ Chunk 6 → "explanation" │ Chunk 7 → "of" │ Chunk 8 → "Kafka"
Spring AI wraps these chunks in a Flux<String> and Spring WebFlux sends each one as an SSE event.
Interview Question
Q: Why does stream().content() return Flux<String> instead of String?
Answer:
-
Stringwaits until the entire response is generated. -
Flux<String>is a reactive stream from Project Reactor. - As soon as the LLM generates a chunk/token, Spring AI emits it.
- This reduces perceived latency and enables ChatGPT-like typing effects.
Spring AI receives each chunk.
Instead of storing everything,
it immediately sends each chunk into a Flux.
Think of Flux as a pipe.
What is Flux?
Imagine a water pipe.
Water Tank ↓ Pipe ↓ Tap
Water comes continuously.
Not all at once.
Flux is exactly the same idea.
Instead of water,
it carries data.
Flux means:
"I don't have one value."
"I have many values coming over time."
Why not List<String>?
Suppose the AI generates:
Kafka is a distributed event streaming platform
List would wait until everything is finished.
Wait... Wait... Wait... Return List
Flux returns immediately.
Kafka ↓ is ↓ a ↓ distributed ↓ ...What does Spring WebFlux do?
Spring AI only creates the Flux.
Someone has to send it to the browser.
That is Spring WebFlux's job.
OpenAI ↓ Spring AI ↓ Flux<String> ↓ Spring WebFlux ↓ Browser
Why do you see
data: Kafka data: is data: a
Because Spring WebFlux converts every Flux element into an SSE message.
Complete Flow
User ↓ Controller ↓ ChatClient ↓ OpenAI "Kafka" ↓ Flux emits ↓ Spring WebFlux ↓ data: Kafka ↓ BrowserWhat is
Flux<String>?
Flux<String>is a reactive stream from Project Reactor that emits multiple values over time.
In Spring AI, each value is typically a chunk of the LLM's generated response, allowing the client to receive text progressively instead of waiting for the entire response.
What Spring WebFlux Does
Spring WebFlux takes each emitted chunk from the
Flux<String>and sends it to the client using Server-Sent Events (SSE).
Q: Why do we use
stream()in Spring AI?
Answer:
stream()enables real-time AI responses.Instead of waiting for the complete response from the LLM,
Spring AI receives generated chunks/tokens continuously and exposes them as a
Flux<String>.Spring WebFlux then streams these chunks to the client using Server-Sent Events (SSE),
improving responsiveness and user experience for AI chat applications.
Comments
Post a Comment