spring-projects/spring-ai · error · IllegalStateException

Unexpected JSON token %s!

Error message

Unexpected JSON token %s!

What it means

The ContentChunk deserializer expects the message content JSON to be either a String or a START_ARRAY token. If the first token is anything else (object, number, boolean), it throws IllegalStateException('Unexpected JSON token %s!').

Source

Thrown at models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java:1575

					List<ContentChunk> contentChunks = new ArrayList<>();

					while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
						jsonToken = jsonParser.currentToken();

						if (jsonToken == JsonToken.START_OBJECT) {
							var contentChunk = jsonParser.readValueAs(ContentChunk.class);
							contentChunks.add(contentChunk);
						}
						else {
							throw new IllegalStateException(
									"Unexpected JSON token %s within the array!".formatted(jsonToken));
						}
					}

					return List.copyOf(contentChunks);
				}

				throw new IllegalStateException("Unexpected JSON token %s!".formatted(jsonToken));
			}

		}

	}

	/**
	 * Represents a chat completion response returned by model, based on the provided
	 * input.
	 *
	 * @param id A unique identifier for the chat completion.
	 * @param object The object type, which is always chat.completion.
	 * @param created The Unix timestamp (in seconds) of when the chat completion was
	 * created.
	 * @param model The model used for the chat completion.
	 * @param choices A list of chat completion choices.
	 * @param usage Usage statistics for the completion request.
	 */

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Inspect the raw response JSON and ensure 'content' is either a string or an array of objects.
  2. Fix or regenerate mocks/recorded fixtures to the correct schema.
  3. Upgrade the Spring AI Mistral module if the API response shape changed.

Example fix

// before
{"content": {"text": "hi"}}
// after
{"content": "hi"}
Defensive patterns

Strategy: try-catch

Validate before calling

JsonNode c = payload.get("content");
if (c != null && !(c.isTextual() || c.isArray())) {
    throw new IllegalStateException("content must be string or array, got: " + c.getNodeType());
}

Try / catch

try {
    ChatResponse r = chatModel.call(prompt);
} catch (IllegalStateException e) {
    if (e.getMessage().matches("Unexpected JSON token .*")) { /* inspect raw response body, fix gateway/fixtures */ }
}

Prevention

When it happens

Trigger: Deserializing a Mistral response where 'content' is a JSON object or other non-string, non-array value, e.g. {"content": {"text": "hi"}}.

Common situations: Proxy/gateway layers rewriting the response body; API version drift between Mistral and the pinned Spring AI client; corrupted or hand-modified recorded responses.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — 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/8a56996b93cb7f72. Report an issue: GitHub.