Streaming Responses in Java and C# with the Official SDKs
Claude streams responses over server-sent events, and the Java and C# SDKs each expose that stream through their platform's native mechanism: a blocking iterator in Java, an async enumerable in C#. This page shows both side by side.
Summary
Streaming lets a caller start rendering Claude's reply as tokens arrive, instead of waiting for the full response.
Under the hood, every official SDK is consuming the same sequence of server-sent events: a message_start event, a series of content_block_delta events carrying text or tool_use fragments, and a message_stop event closing things out.
Java surfaces that sequence through a synchronous streaming iterator you loop over, matching the way Java's SDK exposes blocking calls generally.
C# surfaces it through an async streaming pattern built on IAsyncEnumerable or an event-callback style, matching .NET's async/await conventions.
Both end up producing the same practical result: your code sees text as it's generated, plus a final assembled message once the stream closes.
Recipe
Quick-reference recipe card - copy-paste ready.
StreamResponse<MessageStreamEvent> stream = client.messages().createStreaming(
MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_5)
.maxTokens(512)
.addUserMessage("Write a haiku about streaming data.")
.build()
);
stream.stream().forEach(event -> {
if (event.isContentBlockDelta()) {
event.asContentBlockDelta().delta().text().ifPresent(System.out::print);
}
});await foreach (var streamEvent in client.Messages.StreamClaudeMessageAsync(new MessageParameters
{
Model = AnthropicModels.ClaudeSonnet5,
MaxTokens = 512,
Messages = new List<Message> { new Message(RoleType.User, "Write a haiku about streaming data.") }
}))
{
if (streamEvent is TextDeltaEvent textDelta)
{
Console.Write(textDelta.Text);
}
}When to reach for this:
- You're building a chat UI or CLI tool where users should see output as it's generated, not after a multi-second wait.
- You want to start post-processing text (like sentence-level TTS or incremental logging) before the full response finishes.
- You're streaming a
tool_useblock's arguments as they're generated, for latency-sensitive tool integrations.
Working Example
A complete Java program and a complete C# program, each streaming a response and printing text deltas as they arrive.
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.http.StreamResponse;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.MessageStreamEvent;
import com.anthropic.models.messages.Model;
public class StreamingExample {
public static void main(String[] args) {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_5)
.maxTokens(512)
.addUserMessage("List three benefits of server-sent events.")
.build();
try (StreamResponse<MessageStreamEvent> stream = client.messages().createStreaming(params)) {
stream.stream().forEach(event -> {
if (event.isContentBlockDelta()) {
event.asContentBlockDelta().delta().text().ifPresent(System.out::print);
} else if (event.isMessageStop()) {
System.out.println();
System.out.println("[stream complete]");
}
});
}
}
}using Anthropic.SDK;
using Anthropic.SDK.Messaging;
var client = new AnthropicClient(Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"));
var parameters = new MessageParameters
{
Model = AnthropicModels.ClaudeSonnet5,
MaxTokens = 512,
Messages = new List<Message>
{
new Message(RoleType.User, "List three benefits of server-sent events.")
}
};
await foreach (var streamEvent in client.Messages.StreamClaudeMessageAsync(parameters))
{
switch (streamEvent)
{
case TextDeltaEvent textDelta:
Console.Write(textDelta.Text);
break;
case MessageStopEvent:
Console.WriteLine();
Console.WriteLine("[stream complete]");
break;
}
}What this demonstrates:
- Java's
StreamResponseis anAutoCloseableiterator consumed withstream(), so wrapping it in a try-with-resources block ensures the underlying HTTP connection closes even on an exception. - Java distinguishes event types with
isContentBlockDelta()/asContentBlockDelta()style checked casts, mirroring how it reads regular content blocks. - C#'s
await foreachover anIAsyncEnumerableis the idiomatic .NET way to consume any async stream, not something specific to this SDK. - C# distinguishes event types with a
switchpattern match on the event's runtime type, the async-stream equivalent of Java'sisX()checks.
Deep Dive
How It Works
- Both SDKs open a single HTTP connection with
Accept: text/event-streamand keep it open until the model finishes or an error occurs. - Each server-sent event on that connection becomes one item in the SDK's iterator or async sequence; the SDK parses the raw event payload into a typed event object before handing it to your code.
- Text arrives incrementally as
content_block_deltaevents; atool_useblock's input arguments can also arrive incrementally as partial JSON deltas rather than all at once. - The stream ends with a
message_stopevent; if the connection drops before that event arrives, both SDKs surface that as an error rather than a silent early stop.
Event Types You'll See
| Event | Meaning |
|---|---|
message_start | The response has begun; carries initial message metadata (ID, model, role). |
content_block_start | A new content block (text or tool_use) has begun. |
content_block_delta | An incremental piece of a content block: a text fragment or a partial JSON fragment for tool_use input. |
content_block_stop | The current content block is complete. |
message_delta | Top-level message metadata updates, including the final stop_reason. |
message_stop | The response is fully complete. |
Java Notes
// Accumulating the full text alongside streaming it, for logging or storage.
StringBuilder fullText = new StringBuilder();
stream.stream().forEach(event -> {
if (event.isContentBlockDelta()) {
event.asContentBlockDelta().delta().text().ifPresent(text -> {
System.out.print(text);
fullText.append(text);
});
}
});
System.out.println("\nFull response: " + fullText);C# Notes
// Accumulating the full text alongside streaming it, using a StringBuilder.
var fullText = new StringBuilder();
await foreach (var streamEvent in client.Messages.StreamClaudeMessageAsync(parameters))
{
if (streamEvent is TextDeltaEvent textDelta)
{
Console.Write(textDelta.Text);
fullText.Append(textDelta.Text);
}
}
Console.WriteLine($"\nFull response: {fullText}");Gotchas
- Not closing the Java stream -
StreamResponseholds an open HTTP connection; failing to close it (or use try-with-resources) leaks connections under load. Fix: always wrap streaming calls in try-with-resources or explicitly callclose(). - Blocking the calling thread in C# with
.Resultor.Wait()- calling a streaming method synchronously defeats the purpose ofIAsyncEnumerableand risks deadlocks in some hosting contexts. Fix: useawait foreachinside anasyncmethod all the way up the call chain. - Assuming text arrives in complete words or sentences -
content_block_deltafragments can split mid-word. Fix: buffer and render incrementally without assuming delta boundaries align with anything linguistic. - Parsing tool_use input JSON before the stream completes - partial JSON deltas for a tool_use block are not valid JSON on their own until fully assembled. Fix: accumulate the raw fragments and only parse once
content_block_stopfires for that block. - Ignoring
message_delta'sstop_reason- code that only reads text deltas can miss that the response stopped early due tomax_tokensrather than completing naturally. Fix: check the finalstop_reasonfrommessage_deltaor the assembled message before treating output as complete. - Treating a dropped connection as a normal end of stream - both SDKs distinguish a clean
message_stopfrom a connection failure, but code that doesn't check for stream errors can silently treat a truncated response as complete. Fix: handle the exception or error path both SDKs expose alongside the happy-path iteration.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Non-streaming Messages.New / synchronous call | The full response is needed before any processing can start anyway (e.g. structured output you must fully parse) | You're building an interactive UI where perceived latency matters |
| Go's streaming iterator | Your service is written in Go rather than Java or C# | You're specifically working in the JVM or .NET ecosystem |
| OpenAI compatibility layer's streaming | You're mid-migration from an existing OpenAI-integrated Java or C# codebase and want the OpenAI-shaped chunk iterator temporarily | You want full native access to Claude-specific streaming event types like tool_use deltas |
FAQs
Does Java's streaming call block the calling thread?
Yes, Java's stream() iteration is synchronous/blocking, matching how the rest of the Java SDK exposes calls. Run it on a background thread if you need to keep a UI thread responsive.
Is C#'s streaming call actually async?
Yes, it's built on IAsyncEnumerable and consumed with await foreach, following .NET's standard async streaming convention. Avoid blocking on it synchronously with .Result or .Wait().
What event type carries the actual text as it's generated?
content_block_delta events carry incremental text (or partial tool_use JSON) fragments. Both SDKs expose this as a typed delta you check for text content.
How do I know when the stream is completely done?
Both SDKs surface a message_stop event (or its typed equivalent) as the final item in the stream. Check for it explicitly rather than assuming the loop ending means success.
Can I stream a tool_use block's arguments?
Yes. A tool_use block's input can arrive as partial JSON deltas across multiple content_block_delta events; accumulate the fragments and only parse the JSON once the block's content_block_stop event fires.
What happens if the network connection drops mid-stream?
Both SDKs surface this as an error distinct from a clean message_stop. Handle that error path explicitly so a truncated response isn't mistaken for a complete one.
Do I need to manually close the Java stream?
Yes, unless you use try-with-resources, since StreamResponse holds an open HTTP connection that should be released when you're done.
Is the underlying protocol different between Java and C#?
No. Both consume the same server-sent events from the same Messages API streaming endpoint. Only the language-level mechanism for iterating over those events differs.
Can I accumulate the full response text while also streaming it?
Yes, in both languages. Append each text delta to a string builder (StringBuilder in both Java and C#) as it arrives, alongside whatever incremental rendering you're doing.
Does Go support streaming the same way?
Go exposes streaming through its own iterator pattern, consuming the same underlying server-sent events. See the Go-specific article in this section for that example.
Related
- Calling Claude from Go with the Official SDK - the equivalent streaming pattern in Go, using its own iterator convention
- Other Official SDKs Basics - non-streaming first calls in Java and C#, before adding streaming
- How Server-Sent Events Power Claude's Streaming Responses - the underlying event protocol both SDKs consume here
- Handling Partial JSON During Streamed Tool Calls - a deeper look at the tool_use delta accumulation mentioned in Gotchas
- Streaming Event Types Reference - the full catalog of server-sent event types referenced in the table above
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the current official SDKs for Go, Java, C#, PHP, and Ruby. Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.