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.
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.
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_use block's arguments as they're generated, for latency-sensitive tool integrations.
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 StreamResponse is an AutoCloseable iterator consumed with stream(), 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 foreach over an IAsyncEnumerable is the idiomatic .NET way to consume any async stream, not something specific to this SDK.
C# distinguishes event types with a switch pattern match on the event's runtime type, the async-stream equivalent of Java's isX() checks.
Both SDKs open a single HTTP connection with Accept: text/event-stream and 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_delta events; a tool_use block's input arguments can also arrive incrementally as partial JSON deltas rather than all at once.
The stream ends with a message_stop event; if the connection drops before that event arrives, both SDKs surface that as an error rather than a silent early stop.
// 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);
// 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}");
Not closing the Java stream - StreamResponse holds 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 call close().
Blocking the calling thread in C# with .Result or .Wait() - calling a streaming method synchronously defeats the purpose of IAsyncEnumerable and risks deadlocks in some hosting contexts. Fix: use await foreach inside an async method all the way up the call chain.
Assuming text arrives in complete words or sentences - content_block_delta fragments 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_stop fires for that block.
Ignoring message_delta's stop_reason - code that only reads text deltas can miss that the response stopped early due to max_tokens rather than completing naturally. Fix: check the final stop_reason from message_delta or the assembled message before treating output as complete.
Treating a dropped connection as a normal end of stream - both SDKs distinguish a clean message_stop from 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.
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.
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.
Reviewed by Chris St. John·Last updated Jul 19, 2026