{"record":{"id":"feb17a16debf1701","repo":"spring-projects/spring-ai","slug":"conversion-from-json-to-s-failed","errorCode":null,"errorMessage":"Conversion from JSON to %s failed","messagePattern":"Conversion from JSON to (.+?) failed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java","lineNumber":662,"sourceCode":"\t\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t\t.map(toolCall -> {\n\t\t\t\t\t\t\t\tChatCompletionMessageFunctionToolCall.Builder toolCallBuilder = ChatCompletionMessageFunctionToolCall\n\t\t\t\t\t\t\t\t\t.builder()\n\t\t\t\t\t\t\t\t\t.id(toolCall.id())\n\t\t\t\t\t\t\t\t\t.function(ChatCompletionMessageFunctionToolCall.Function.builder()\n\t\t\t\t\t\t\t\t\t\t.name(toolCall.name())\n\t\t\t\t\t\t\t\t\t\t.arguments(toolCall.arguments())\n\t\t\t\t\t\t\t\t\t\t.build());\n\n\t\t\t\t\t\t\t\tString jsonProps = toolCallAdditionalProperties.get(toolCall.id());\n\t\t\t\t\t\t\t\tif (StringUtils.hasText(jsonProps)) {\n\t\t\t\t\t\t\t\t\tMap<String, JsonValue> additionalProperties = new LinkedHashMap<>();\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tobjectMapper.readValue(jsonProps, MAP_TYPE_REF)\n\t\t\t\t\t\t\t\t\t\t\t.forEach((k, v) -> additionalProperties.put(k, JsonValue.from(v)));\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tcatch (JsonProcessingException ex) {\n\t\t\t\t\t\t\t\t\t\tthrow new IllegalStateException(\"Conversion from JSON to %s failed\"\n\t\t\t\t\t\t\t\t\t\t\t.formatted(MAP_TYPE_REF.getType().getTypeName()), ex);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttoolCallBuilder.putAllAdditionalProperties(additionalProperties);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn ChatCompletionMessageToolCall.ofFunction(toolCallBuilder.build());\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.toList();\n\n\t\t\t\t\t\tbuilder.toolCalls(toolCalls);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Replay reasoning content only when present - plain OpenAI is\n\t\t\t\t\t// unaffected\n\t\t\t\t\tObject reasoningContent = assistantMessage.getMetadata().get(REASONING_CONTENT);\n\t\t\t\t\tif (reasoningContent instanceof String reasoning && StringUtils.hasText(reasoning)) {\n\t\t\t\t\t\t// \"reasoning_content\" is the wire field; REASONING_CONTENT is the\n\t\t\t\t\t\t// metadata key\n\t\t\t\t\t\tbuilder.putAdditionalProperty(\"reasoning_content\", JsonValue.from(reasoning));","sourceCodeStart":644,"sourceCodeEnd":680,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java#L644-L680","documentation":"When building tool-call messages, OpenAiChatModel.createRequest() deserializes the raw JSON arguments string of each tool call into a Map<String,Object> (MAP_TYPE_REF) and converts it to JsonValue additional properties. If the arguments string is not valid JSON, Jackson throws JsonProcessingException and the model wraps it in an IllegalStateException with this message, since the map type name is formatted into the text.","triggerScenarios":"An assistant tool-call message whose function arguments string is malformed JSON — e.g. truncated arguments, arguments produced by an LLM that emitted invalid JSON, or manually constructed ToolResponse/assistant messages with arguments set to non-JSON text — while re-creating a ChatCompletionRequest for the follow-up round trip.","commonSituations":"Feeding back conversation history where the model emitted invalid/truncated tool-call JSON (long arguments cut off by max_tokens); persisting chat memory with escaped or double-encoded JSON and replaying it; hand-crafting tool call messages in tests; switching models whose tool-call JSON is malformed.","solutions":["Validate the tool call's arguments string parses as JSON before adding the message to history (e.g. objectMapper.readValue(args, Map.class) in a pre-check).","Truncate or drop the malformed tool-call message from conversation history and ask the model to retry the tool call.","Check max_tokens/output limits that truncate tool-call JSON and increase them.","If arguments are stored in memory/persistence, verify they were not double-encoded or altered; re-serialize the original Map instead of a string round-trip.","Inspect the wrapped JsonProcessingException cause to locate the exact JSON syntax error."],"exampleFix":"// before\nmessages.add(new AssistantMessage.ToolCall(id, \"function\", name, invalidArgsJson));\n// after\nString args = invalidArgsJson;\ntry {\n    objectMapper.readTree(args); // fail fast with a clear message\n}\ncatch (JsonProcessingException e) {\n    args = \"{\\\"_raw\\\":\\\"\\\"}\"; // or drop/re-request the tool call\n}\nmessages.add(new AssistantMessage.ToolCall(id, \"function\", name, args));","handlingStrategy":"try-catch","validationCode":"// Validate tool-call arguments JSON before adding to conversation history\nvoid requireValidJson(String args) {\n    try { objectMapper.readTree(args); }\n    catch (JsonProcessingException e) {\n        throw new IllegalArgumentException(\"Tool call arguments are not valid JSON\", e);\n    }\n}","typeGuard":"boolean isValidJson(String s) {\n    if (s == null || s.isBlank()) return false;\n    try { objectMapper.readTree(s); return true; }\n    catch (JsonProcessingException e) { return false; }\n}","tryCatchPattern":"try {\n    ChatResponse response = chatModel.call(promptWithHistory);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Conversion from JSON to\")) {\n        // drop/repair the malformed tool-call message in history and retry\n    } else throw e;\n}","preventionTips":["Validate every tool-call arguments string with a JSON parse before persisting it to chat memory.","Set max_tokens high enough that tool-call JSON is never truncated mid-string.","Store the parsed arguments Map rather than round-tripping an arguments String in persistence.","When replaying history, drop tool-call messages whose arguments fail JSON validation and re-prompt the model.","Increase tool JSON reliability by keeping tool schemas simple (avoid deeply nested required fields)."],"tags":["json","openai","tool-calls","deserialization","chat"],"backgroundTag":"json-parse-error","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}