spring-projects/spring-ai · error · IllegalStateException
Cannot deserialize ThinkOption from token:
Error message
Cannot deserialize ThinkOption from token:
What it means
The ThinkOption custom Jackson deserializer only accepts JSON boolean tokens (true/false -> ThinkBoolean.ENABLED/DISABLED), string tokens (-> ThinkLevel), and null. Any other JSON token — a number, array, or object — reaches the final throw, so the library raises an IllegalStateException because the 'think' field in an Ollama request/response has an unsupported JSON shape.
Source
Thrown at models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/ThinkOption.java:92
*/
class ThinkOptionDeserializer extends ValueDeserializer<ThinkOption> {
@Override
public @Nullable ThinkOption deserialize(JsonParser p, DeserializationContext ctxt) {
JsonToken token = p.currentToken();
if (token == JsonToken.VALUE_TRUE) {
return ThinkBoolean.ENABLED;
}
else if (token == JsonToken.VALUE_FALSE) {
return ThinkBoolean.DISABLED;
}
else if (token == JsonToken.VALUE_STRING) {
return new ThinkLevel(p.getValueAsString());
}
else if (token == JsonToken.VALUE_NULL) {
return null;
}
throw new IllegalStateException("Cannot deserialize ThinkOption from token: " + token);
}
}
/**
* Boolean-style think option for models that support simple enable/disable. Supported
* by Qwen 3, DeepSeek-v3.1, and DeepSeek R1 models.
*
* @param enabled whether thinking is enabled
*/
record ThinkBoolean(boolean enabled) implements ThinkOption {
/**
* Constant for enabled thinking.
*/
public static final ThinkBoolean ENABLED = new ThinkBoolean(true);
/**View on GitHub (pinned to 98a7beda4f)
Solutions
- Change the think value to a JSON boolean (true/false) for Qwen 3, DeepSeek-v3.1, or DeepSeek R1 models.
- For GPT-OSS, change the value to a quoted string "low", "medium", or "high" (or use ThinkLevel.LOW/MEDIUM/HIGH constants).
- If the value comes from config, quote it or normalize it before building the request.
- If the payload comes from the server/proxy, inspect the raw JSON to see the actual token type and fix the producer.
Example fix
// before
{"model":"gpt-oss","think":1}
// after
{"model":"gpt-oss","think":"medium"} Defensive patterns
Strategy: validation
Validate before calling
// Normalize a think config value before it reaches ThinkOption deserialization
Object raw = config.get("think");
if (!(raw instanceof Boolean) && !(raw instanceof String s && List.of("low","medium","high").contains(s.toLowerCase())) && raw != null) {
throw new IllegalArgumentException("think must be boolean, one of low/medium/high, or null; got: " + raw);
} Type guard
boolean isValidThinkValue(Object v) {
return v == null || v instanceof Boolean
|| (v instanceof String s && List.of("low", "medium", "high").contains(s.toLowerCase(Locale.ROOT)));
} Try / catch
try {
ThinkOption think = mapper.readValue(json, RequestPayload.class).think();
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Cannot deserialize ThinkOption")) {
logger.warn("Unsupported 'think' JSON token, defaulting to null", e);
think = null;
} else throw e;
} Prevention
- Always send think as a JSON boolean (Qwen3/DeepSeek) or quoted string low/medium/high (GPT-OSS).
- Quote string values in YAML/JSON config so parsers don't coerce them.
- Never pass numeric 0/1 for think; convert numbers to booleans before building requests.
- Add a startup config validation step for think-related settings.
When it happens
Trigger: Deserializing a payload where the 'think' field is a JSON number (e.g. think: 1), array, or object instead of a boolean or a string like "low"/"medium"/"high"; typically happens when a hand-crafted request body, a config file, or a proxied Ollama response supplies a numeric or structured think value.
Common situations: Hand-editing request JSON and writing think: 1 instead of true; sending an Ollama API response captured from a different Ollama version with a differently shaped think field; YAML/JSON config where an unquoted low is parsed as something other than a string; wrapping/serializing ThinkOption through a middleware that converts booleans to 0/1.
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
- Conversion from JSON to %s failed
- Invalid JSON format for the response:
- Failed to parse JSON:
- Unexpected JSON token %s within the array!
- Unexpected JSON token %s!
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/22decde6af9dd7dd.
Report an issue: GitHub.