spring-projects/spring-ai · error · java.lang.IllegalStateException

Unexpected reasoningContent block: neither reasoningText…

Error message

Unexpected reasoningContent block: neither reasoningText nor redactedContent is set

What it means

BedrockReasoningContent.from converts an AWS SDK ReasoningContentBlock into the value object. If the block has neither redactedContent nor reasoningText set — an invariant the Bedrock API guarantees — this IllegalStateException is thrown, indicating an unexpected/malformed response shape from the service or SDK.

Solutions

  1. Check reasoningContentBlock.reasoningText() and .redactedContent() for null before calling from().
  2. Upgrade the AWS SDK / Spring AI Bedrock dependency — empty reasoning blocks may be an SDK serialization bug.
  3. Treat as an unexpected API response: log the raw block and skip it rather than failing the whole response.
  4. Verify the model actually supports extended thinking and is emitting well-formed reasoning blocks.

Example fix

// before
BedrockReasoningContent rc = BedrockReasoningContent.from(block);
// after
if (block.reasoningText() == null && block.redactedContent() == null) {
    return; // skip malformed empty reasoning block
}
BedrockReasoningContent rc = BedrockReasoningContent.from(block);
Defensive patterns

Strategy: try-catch

Validate before calling

if (block.reasoningText() == null && block.redactedContent() == null) {
    logger.warn("Skipping empty reasoningContent block");
    return null;
}

Type guard

boolean hasReasoningPayload(ReasoningContentBlock b) {
    return b != null && (b.reasoningText() != null || b.redactedContent() != null);
}

Try / catch

try {
    rc = BedrockReasoningContent.from(block);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("neither reasoningText nor redactedContent")) {
        rc = null; // tolerate empty block
    } else throw e;
}

Prevention

When it happens

Trigger: Calling BedrockReasoningContent.from(reasoningContentBlock) on a block where both reasoningText() and redactedContent() are null — a deserialized but empty reasoningContent block from a Bedrock streaming/complete response.

Common situations: SDK version mismatches where an empty reasoning block is emitted by newer models; proxying non-standard responses; hand-constructed ReasoningContentBlock passed to from() without any payload.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/e27e30e3ef989b8a. Report an issue: GitHub.

Appendix: source

Thrown at models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockReasoningContent.java:80

	/**
	 * Capture a Bedrock {@link ReasoningContentBlock} as a Spring AI value object,
	 * preserving its union member (reasoning text and signature, or redacted content)
	 * without modification.
	 * @param reasoningContentBlock the AWS SDK reasoning content block
	 * @return a value object that can replay the same reasoning block
	 */
	public static BedrockReasoningContent from(ReasoningContentBlock reasoningContentBlock) {
		Assert.notNull(reasoningContentBlock, "reasoningContentBlock must not be null");
		SdkBytes redacted = reasoningContentBlock.redactedContent();
		if (redacted != null) {
			return new BedrockReasoningContent(null, null, redacted.asByteArray());
		}
		ReasoningTextBlock reasoningText = reasoningContentBlock.reasoningText();
		if (reasoningText != null) {
			return new BedrockReasoningContent(reasoningText.text(), reasoningText.signature(), null);
		}
		throw new IllegalStateException(
				"Unexpected reasoningContent block: neither reasoningText nor redactedContent is set");
	}

	/**
	 * Convert this value object back into an AWS SDK {@link ContentBlock} carrying a
	 * {@code reasoningContent} block. The reasoning text and signature are replayed
	 * unmodified; signatures are never regenerated.
	 * @return the reasoning content block ready to be added to a Bedrock request
	 */
	public ContentBlock toContentBlock() {
		ReasoningContentBlock.Builder builder = ReasoningContentBlock.builder();
		if (this.redactedContent != null) {
			builder.redactedContent(SdkBytes.fromByteArray(this.redactedContent));
		}
		else {
			builder.reasoningText(ReasoningTextBlock.builder().text(this.text).signature(this.signature).build());
		}
		return ContentBlock.fromReasoningContent(builder.build());

View on GitHub (pinned to 98a7beda4f)