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

Failed to parse JSON:

Error message

Failed to parse JSON: 

What it means

parseJsonToMap() parses model/function-call JSON payloads into a Map, wrapping non-object results under a "result" key. If the JSON cannot be parsed at all, the exception (including the offending json string) is rethrown as a RuntimeException with this message. It is raised when converting tool-call argument or response payloads during Gemini function calling.

Source

Thrown at models/spring-ai-google-genai/src/main/java/org/springframework/ai/google/genai/GoogleGenAiChatModel.java:376

			if (parsed instanceof List) {
				// It's an array, wrap it in a map with "result" key
				Map<String, Object> wrapper = new HashMap<>();
				wrapper.put("result", parsed);
				return wrapper;
			}
			else if (parsed instanceof Map) {
				// It's already a map, return it
				return (Map<String, Object>) parsed;
			}
			else {
				// It's a primitive or other type, wrap it
				Map<String, Object> wrapper = new HashMap<>();
				wrapper.put("result", parsed);
				return wrapper;
			}
		}
		catch (Exception e) {
			throw new RuntimeException("Failed to parse JSON: " + json, e);
		}
	}

	private String mapToJson(Map<String, Object> map) {
		try {
			return this.jsonMapper.writeValueAsString(map);
		}
		catch (Exception e) {
			throw new RuntimeException("Failed to convert map to JSON", e);
		}
	}

	private Schema jsonToSchema(String json) {
		try {
			return this.jsonMapper.readValue(json, Schema.class);
		}
		catch (Exception e) {
			throw new RuntimeException(e);

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Log the offending 'json' payload from the RuntimeException message and sanitize/repair the source producing it (often the model output).
  2. Tighten function tool descriptions and use Gemini's response schema so the model produces well-formed JSON arguments.
  3. Wrap tool invocation points with a defensive parse: catch the RuntimeException and return a structured error result back to the model instead of crashing.

Example fix

// before
Map<String, Object> args = model.parseJsonToMap(raw); // throws on malformed model output

// after
Map<String, Object> args;
try {
    args = model.parseJsonToMap(raw);
}
catch (RuntimeException e) {
    logger.warn("Malformed tool JSON: {}", e.getMessage());
    args = Map.of();
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    Map<String, Object> args = parseJsonToMap(json);
}
catch (RuntimeException e) {
    logger.warn("Malformed JSON payload: {}", e.getMessage());
    // return structured error result to the model or fall back
}

Prevention

When it happens

Trigger: A Gemini function call whose argument/response string is not valid JSON (truncated output, model-generated malformed JSON, or raw text where JSON was expected) is passed through messageToGeminiParts/partBuilder's parseJsonToMap path.

Common situations: Model emitting malformed or truncated JSON in function-call arguments (hit output-token limits); custom tools returning non-JSON strings that get re-parsed; embedded control characters or single-quoted pseudo-JSON from the model.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


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