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

  1. Change the think value to a JSON boolean (true/false) for Qwen 3, DeepSeek-v3.1, or DeepSeek R1 models.
  2. For GPT-OSS, change the value to a quoted string "low", "medium", or "high" (or use ThinkLevel.LOW/MEDIUM/HIGH constants).
  3. If the value comes from config, quote it or normalize it before building the request.
  4. 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

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


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