spring-projects/spring-ai · error · IllegalArgumentException
Failed to parse JSON schema:
Error message
Failed to parse JSON schema:
What it means
When OpenAiChatOptions.responseFormat has type JSON_SCHEMA, createRequest parses responseFormat.getJsonSchema() (a JSON string) into the SDK's JsonSchema object via Jackson. Any exception during parsing — malformed JSON, a structure that doesn't deserialize into ResponseFormatJsonSchema.JsonSchema.Schema, or a null/empty string coerced to "" — is wrapped and rethrown as this IllegalArgumentException with the offending schema string as the message and the original cause attached.
Source
Thrown at models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java:791
String jsonSchemaString = responseFormat.getJsonSchema() != null ? responseFormat.getJsonSchema() : "";
try {
ResponseFormatJsonSchema.JsonSchema.Builder jsonSchemaBuilder = ResponseFormatJsonSchema.JsonSchema
.builder();
jsonSchemaBuilder.name("json_schema");
Boolean strict = responseFormat.getStrict();
jsonSchemaBuilder.strict(strict != null ? strict : true);
ResponseFormatJsonSchema.JsonSchema.Schema schema = objectMapper.readValue(jsonSchemaString,
ResponseFormatJsonSchema.JsonSchema.Schema.class);
jsonSchemaBuilder.schema(schema);
builder.responseFormat(
ResponseFormatJsonSchema.builder().jsonSchema(jsonSchemaBuilder.build()).build());
}
catch (Exception e) {
throw new IllegalArgumentException("Failed to parse JSON schema: " + jsonSchemaString, e);
}
}
else {
throw new IllegalArgumentException("Unsupported response format type: " + responseFormat.getType());
}
}
if (requestOptions.getSeed() != null) {
builder.seed(requestOptions.getSeed());
}
if (requestOptions.getStop() != null && !requestOptions.getStop().isEmpty()) {
if (requestOptions.getStop().size() == 1) {
builder.stop(ChatCompletionCreateParams.Stop.ofString(requestOptions.getStop().get(0)));
}
else {
builder.stop(ChatCompletionCreateParams.Stop.ofStrings(requestOptions.getStop()));
}
}
if (requestOptions.getTemperature() != null) {View on GitHub (pinned to 98a7beda4f)
Solutions
- Validate that the jsonSchema string is well-formed JSON before setting it: parse it with ObjectMapper.readTree() in a pre-check.
- Ensure the string is a JSON Schema object starting with '{' (e.g. {"type":"object","properties":{...}}), not null, empty, or a non-object JSON value.
- Generate the schema string with a serializer (new ObjectMapper().writeValueAsString(schemaMap)) instead of hand-writing or concatenating it.
- Read the wrapped cause exception (e.getCause()) — it pinpoints the exact Jackson deserialization problem, e.g. unexpected token or unmapped field.
Example fix
// before
options.setResponseFormat(new ResponseFormat(JSON_SCHEMA, "{type: 'object'}")); // invalid JSON (single quotes)
// after
String schema = "{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"]}";
new ObjectMapper().readTree(schema); // validate first
options.setResponseFormat(new ResponseFormat(JSON_SCHEMA, schema)); Defensive patterns
Strategy: validation
Validate before calling
public static String validateJsonSchema(String schema) {
if (schema == null || schema.isBlank()) throw new IllegalArgumentException("jsonSchema must be a non-empty JSON object");
try {
com.fasterxml.jackson.databind.JsonNode n = new com.fasterxml.jackson.databind.ObjectMapper().readTree(schema);
if (!n.isObject()) throw new IllegalArgumentException("jsonSchema must be a JSON object");
} catch (com.fasterxml.jackson.core.JsonProcessingException e) {
throw new IllegalArgumentException("jsonSchema is not valid JSON", e);
}
return schema;
} Try / catch
try {
return chatModel.call(prompt);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Failed to parse JSON schema:")) {
logger.error("Check the schema string and cause", e.getCause());
prompt.getOptions().setResponseFormat(new ResponseFormat(ResponseFormat.Type.JSON_OBJECT, null));
return chatModel.call(prompt);
}
throw e;
} Prevention
- Serialize schemas from typed objects/maps with Jackson instead of hand-writing JSON strings.
- Validate the schema string with readTree() before assigning it to ResponseFormat.
- Never pass null or empty jsonSchema when type is JSON_SCHEMA (it defaults to "" and fails).
- Log e.getCause() when this error fires — the Jackson message pinpoints the offending token.
When it happens
Trigger: Setting OpenAiChatOptions.builder().responseFormat(new ResponseFormat(ResponseFormat.Type.JSON_SCHEMA, schemaJson)) where schemaJson is not valid JSON, is an empty/null string (defaults to ""), uses JSON constructs Jackson cannot map to the Schema type (e.g. top-level non-object), or contains invalid JSON Schema keywords that break deserialization.
Common situations: Hand-editing a schema and leaving a trailing comma; building the schema string with string concatenation producing invalid JSON; passing a Java object's toString() instead of JSON; forgetting to set getJsonSchema() so it parses ""; loading the schema from a file/resource that failed to load and returned null.
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to parse toolChoice JSON:
- Unsupported message type:
- Unsupported response format type:
- Unknown tool_choice type:
- Failed to parse JSON schema
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/5317dc9da4c32c7f.
Report an issue: GitHub.