Describe the bug
Summary
When calling BedrockRuntimeAsyncClient.converseStream() with a tool configuration, the SDK never delivers a ContentBlockStopEvent for tool_use content blocks. After the last ContentBlockDeltaEvent for the tool input, the stream goes completely silent — MessageStopEvent also never arrives. The
connection holds open until the HTTP read/SSE timeout fires.
Text content blocks (type=text) receive their ContentBlockStopEvent correctly. Only tool_use blocks are affected.
SDK version
software.amazon.awssdk:bedrockruntime:2.47.4
Region / Model
- Region: eu-central-1
- Model: eu.anthropic.claude-sonnet-4-6
Observed event sequence
MessageStartEvent
ContentBlockDeltaEvent (text, index 0) ← multiple tokens
ContentBlockDeltaEvent (text, index 0)
ContentBlockStopEvent (index 0) ← text block closes correctly ✓
ContentBlockStartEvent (index 1, tool_use)
ContentBlockDeltaEvent (index 1, tool_use, partial_json="")
← SILENCE — no ContentBlockStopEvent
← no MessageStopEvent
← stream holds open until timeout
Expected event sequence
MessageStartEvent
ContentBlockDeltaEvent (text, index 0)
...
ContentBlockStopEvent (index 0)
ContentBlockStartEvent (index 1, tool_use)
ContentBlockDeltaEvent (index 1, tool_use)
ContentBlockStopEvent (index 1) ← never arrives
MessageStopEvent ← never arrives
ConverseStreamMetadataEvent
Minimal reproduction
var request = ConverseStreamRequest.builder()
.modelId("eu.anthropic.claude-sonnet-4-6")
.messages(Message.builder()
.role(ConversationRole.USER)
.content(ContentBlock.builder()
.text("Please analyse my company benefit data and give me a short summary.")
.build())
.build())
.toolConfig(ToolConfiguration.builder()
.tools(Tool.builder()
.toolSpec(ts -> ts
.name("CompanyBenefitData")
.description("Return benefit health data for a company.")
.inputSchema(is -> is.json(Document.fromMap(Map.of(
"type", Document.fromString("object"),
"properties", Document.fromMap(Map.of()),
"required", Document.fromList(List.of()))))))
.build())
.build())
.build();
asyncClient.converseStream(request, ConverseStreamResponseHandler.builder()
.subscriber(event -> System.out.println("Event: " + event.getClass().getSimpleName()))
.onComplete(() -> System.out.println("Complete"))
.onError(t -> System.out.println("Error: " + t))
.build())
.get(90, TimeUnit.SECONDS);
// Never prints "Complete" or "Error" within 90s on a tool_use turn
// ContentBlockStopEvent for index 1 never appears in subscriber
Additional observations
- The ConverseStream raw HTTP/2 connection does receive DATA frames from Bedrock after the last tool_use_delta (observed via Netty's DefaultHttp2ConnectionDecoder logging). Those frames are not translated into ContentBlockStopEvent by the SDK.
- The synchronous converse() API correctly returns the complete response including tool_use blocks — only the streaming path is affected.
- This appears to be an SDK-level translation issue: the raw Bedrock response contains the stop events but they are not surfaced to the ConverseStreamResponseHandler subscriber.
Regression Issue
Expected Behavior
MessageStartEvent
ContentBlockDeltaEvent (text, index 0)
...
ContentBlockStopEvent (index 0)
ContentBlockStartEvent (index 1, tool_use)
ContentBlockDeltaEvent (index 1, tool_use)
ContentBlockStopEvent (index 1) ← never arrives
MessageStopEvent ← never arrives
ConverseStreamMetadataEvent
Current Behavior
After the last ContentBlockDeltaEvent for the tool_use block, the stream goes completely silent. ContentBlockStopEvent for index=1 never arrives. MessageStopEvent never arrives. onComplete is never called. The CompletableFuture returned by converseStream() hangs until the caller's timeout fires.
Observed SDK event log (INFO-level instrumentation on the ConverseStreamResponseHandler subscriber):
[bedrock-stream] event received: DefaultMessageStart
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockStop ← index 0 (text), correct
[bedrock-stream] ContentBlockStopEvent index=0
[bedrock-stream] event received: DefaultContentBlockStart
[bedrock-stream] ContentBlockStartEvent index=1 type=tool_use
[bedrock-stream] event received: DefaultContentBlockDelta ← tool input delta
← SILENCE from this point
← no ContentBlockStopEvent
← no MessageStopEvent
← onComplete never called
← onError never called
After a 60 s wall-clock wait:
java.util.concurrent.TimeoutException
at java.base/java.util.concurrent.CompletableFuture.timedGet(CompletableFuture.java:1957)
at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2031)
at ConverseStreamToolUseBug.main(ConverseStreamToolUseBug.java:XX)
Reproduction Steps
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.core.document.Document;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient;
import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock;
import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole;
import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamRequest;
import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamResponseHandler;
import software.amazon.awssdk.services.bedrockruntime.model.Message;
import software.amazon.awssdk.services.bedrockruntime.model.Tool;
import software.amazon.awssdk.services.bedrockruntime.model.ToolConfiguration;
/**
* Reproduces: ContentBlockStopEvent never delivered for tool_use blocks in ConverseStream.
*
* Dependencies (Gradle):
* implementation platform("software.amazon.awssdk:bom:2.47.4")
* implementation "software.amazon.awssdk:bedrockruntime"
* implementation "software.amazon.awssdk:netty-nio-client"
*
* Prerequisites:
* AWS credentials available via the default credentials chain
* (env vars, ~/.aws/credentials, or IAM role).
*
* Expected: subscriber prints ContentBlockStopEvent for index 1, then MessageStopEvent, then "onComplete".
* Actual: subscriber stops after ContentBlockDeltaEvent for index 1 — no ContentBlockStopEvent,
* no MessageStopEvent, no "onComplete" within the 60s timeout.
*/
public class ConverseStreamToolUseBug {
public static void main(String[] args) throws Exception {
var client = BedrockRuntimeAsyncClient.builder()
.region(Region.EU_CENTRAL_1)
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
var request = ConverseStreamRequest.builder()
.modelId("eu.anthropic.claude-sonnet-4-6")
.messages(Message.builder()
.role(ConversationRole.USER)
.content(ContentBlock.builder()
.build())
.build())
.toolConfig(ToolConfiguration.builder()
.tools(Tool.builder()
.toolSpec(ts -> ts
.name("CompanyBenefitData")
.description("Returns benefit data for the user's company.")
.inputSchema(is -> is.json(
Document.fromMap(Map.of(
"type", Document.fromString("object"),
"properties", Document.fromMap(Map.of()),
"required", Document.fromList(List.of()))))))
.build())
.build())
.build();
var handler = ConverseStreamResponseHandler.builder()
.subscriber(event -> System.out.println(
"[" + System.currentTimeMillis() + "] event: " + event.getClass().getSimpleName()))
.onComplete(() -> System.out.println("onComplete — stream finished normally"))
.onError(t -> System.out.println("onError — " + t.getClass().getSimpleName() + ": " + t.getMessage()))
.build();
System.out.println("Starting converseStream...");
client.converseStream(request, handler).get(60, TimeUnit.SECONDS);
client.close();
}
}
Observed output (actual):
Starting converseStream...
[..] event: DefaultMessageStart
[..] event: DefaultContentBlockDelta
[..] event: DefaultContentBlockDelta
[..] event: DefaultContentBlockStop ← index 0 (text) closes correctly
[..] event: DefaultContentBlockStart ← index 1 (tool_use) opens
[..] event: DefaultContentBlockDelta ← tool input delta
← silence until TimeoutException after 60s
← no DefaultContentBlockStop for index 1
← no DefaultMessageStop
← onComplete never called
Expected output:
[..] event: DefaultContentBlockStop ← index 1 (tool_use) should close here
[..] event: DefaultMessageStop
[..] event: DefaultConverseStreamMetadata
onComplete — stream finished normally
Possible Solution
I have done workarounds by implementing watchdog solution but it has downsides, don't have an ideea how could this be fixed.
Additional Information/Context
The synchronous BedrockRuntimeClient.converse() API with the identical request payload completes successfully and returns the full response including the tool_use content block — only the converseStream() path is affected.
AWS Java SDK version used
software.amazon.awssdk:bedrockruntime:2.47.4
JDK version used
java 25.0.1 Oracle
Operating System and version
MacOs 15.7.4
Describe the bug
Summary
When calling
BedrockRuntimeAsyncClient.converseStream()with a tool configuration, the SDK never delivers aContentBlockStopEventfor tool_use content blocks. After the lastContentBlockDeltaEventfor the tool input, the stream goes completely silent —MessageStopEventalso never arrives. Theconnection holds open until the HTTP read/SSE timeout fires.
Text content blocks (type=text) receive their
ContentBlockStopEventcorrectly. Onlytool_useblocks are affected.SDK version
software.amazon.awssdk:bedrockruntime:2.47.4Region / Model
Observed event sequence
Expected event sequence
Minimal reproduction
Additional observations
Regression Issue
Expected Behavior
MessageStartEvent
ContentBlockDeltaEvent (text, index 0)
...
ContentBlockStopEvent (index 0)
ContentBlockStartEvent (index 1, tool_use)
ContentBlockDeltaEvent (index 1, tool_use)
ContentBlockStopEvent (index 1) ← never arrives
MessageStopEvent ← never arrives
ConverseStreamMetadataEvent
Current Behavior
After the last ContentBlockDeltaEvent for the tool_use block, the stream goes completely silent. ContentBlockStopEvent for index=1 never arrives. MessageStopEvent never arrives. onComplete is never called. The CompletableFuture returned by converseStream() hangs until the caller's timeout fires.
Observed SDK event log (INFO-level instrumentation on the ConverseStreamResponseHandler subscriber):
[bedrock-stream] event received: DefaultMessageStart
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockDelta
[bedrock-stream] event received: DefaultContentBlockStop ← index 0 (text), correct
[bedrock-stream] ContentBlockStopEvent index=0
[bedrock-stream] event received: DefaultContentBlockStart
[bedrock-stream] ContentBlockStartEvent index=1 type=tool_use
[bedrock-stream] event received: DefaultContentBlockDelta ← tool input delta
← SILENCE from this point
← no ContentBlockStopEvent
← no MessageStopEvent
← onComplete never called
← onError never called
After a 60 s wall-clock wait:
java.util.concurrent.TimeoutException
at java.base/java.util.concurrent.CompletableFuture.timedGet(CompletableFuture.java:1957)
at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2031)
at ConverseStreamToolUseBug.main(ConverseStreamToolUseBug.java:XX)
Reproduction Steps
Observed output (actual):
Starting converseStream...
[..] event: DefaultMessageStart
[..] event: DefaultContentBlockDelta
[..] event: DefaultContentBlockDelta
[..] event: DefaultContentBlockStop ← index 0 (text) closes correctly
[..] event: DefaultContentBlockStart ← index 1 (tool_use) opens
[..] event: DefaultContentBlockDelta ← tool input delta
← silence until TimeoutException after 60s
← no DefaultContentBlockStop for index 1
← no DefaultMessageStop
← onComplete never called
Expected output:
[..] event: DefaultContentBlockStop ← index 1 (tool_use) should close here
[..] event: DefaultMessageStop
[..] event: DefaultConverseStreamMetadata
onComplete — stream finished normally
Possible Solution
I have done workarounds by implementing watchdog solution but it has downsides, don't have an ideea how could this be fixed.
Additional Information/Context
The synchronous BedrockRuntimeClient.converse() API with the identical request payload completes successfully and returns the full response including the tool_use content block — only the converseStream() path is affected.
AWS Java SDK version used
software.amazon.awssdk:bedrockruntime:2.47.4
JDK version used
java 25.0.1 Oracle
Operating System and version
MacOs 15.7.4