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

  1. Validate that the jsonSchema string is well-formed JSON before setting it: parse it with ObjectMapper.readTree() in a pre-check.
  2. Ensure the string is a JSON Schema object starting with '{' (e.g. {"type":"object","properties":{...}}), not null, empty, or a non-object JSON value.
  3. Generate the schema string with a serializer (new ObjectMapper().writeValueAsString(schemaMap)) instead of hand-writing or concatenating it.
  4. 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

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.

Related errors


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