spring-projects/spring-ai · error · RuntimeException

IOException (wrapped RuntimeException)

Error message

IOException (wrapped RuntimeException)

What it means

JsonReader.get() parses the underlying resource as JSON; if reading/parsing the stream throws IOException (unreadable resource, I/O error mid-stream), it is wrapped in a plain RuntimeException. Malformed JSON that Jackson reports as IOException variants (e.g. JsonParseException extends IOException) also lands here.

Solutions

  1. Verify resource.exists() and that the content is valid JSON before constructing JsonReader
  2. Validate the JSON with a standalone parse (JsonMapper.shared().readTree) to get richer error details
  3. Catch RuntimeException and inspect the cause for the real IOException/JsonParseException
  4. Log resource.getDescription() to confirm which path was actually resolved

Example fix

// before
List<Document> docs = new JsonReader(resource).get(); // RuntimeException on bad content
// after
if (!resource.exists()) throw new IllegalStateException("Missing: " + resource);
JsonMapper.shared().readTree(resource.getInputStream()); // fail early with a clear parse error
List<Document> docs = new JsonReader(resource).get();
Defensive patterns

Strategy: validation

Validate before calling

if (!resource.exists()) throw new IllegalStateException("Missing JSON: " + resource);
try (InputStream in = resource.getInputStream()) {
    JsonMapper.shared().readTree(in); // fail fast with a precise Jackson error
}

Try / catch

try {
    docs = jsonReader.get();
} catch (RuntimeException e) {
    throw new IllegalStateException("JSON read failed: " + resource, e.getCause());
}

Prevention

When it happens

Trigger: Calling jsonReader.get() where the Resource input stream fails — file missing, permission denied, connection dropped — or content that Jackson cannot parse (invalid JSON raising JsonParseException).

Common situations: Pointing JsonReader at a non-JSON file, truncated downloads, or resources inside a jar with wrong classpath path.

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/65b7877f8e4e3630. Report an issue: GitHub.

Appendix: source

Thrown at spring-ai-commons/src/main/java/org/springframework/ai/reader/JsonReader.java:86

		this.jsonKeysToUse = List.of(jsonKeysToUse);
	}

	@Override
	public List<Document> get() {
		try {
			JsonNode rootNode = JsonMapper.shared().readTree(this.resource.getInputStream());

			if (rootNode.isArray()) {
				return StreamSupport.stream(rootNode.spliterator(), true)
					.map(jsonNode -> parseJsonNode(jsonNode, JsonMapper.shared()))
					.toList();
			}
			else {
				return Collections.singletonList(parseJsonNode(rootNode, JsonMapper.shared()));
			}
		}
		catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private Document parseJsonNode(JsonNode jsonNode, JsonMapper jsonMapper) {
		Map<String, Object> item = jsonMapper.convertValue(jsonNode, new TypeReference<>() {

		});
		var sb = new StringBuilder();

		this.jsonKeysToUse.stream()
			.filter(item::containsKey)
			.forEach(key -> sb.append(key).append(": ").append(item.get(key)).append(System.lineSeparator()));

		Map<String, Object> metadata = this.jsonMetadataGenerator.generate(item);
		String content = sb.isEmpty() ? item.toString() : sb.toString();
		return new Document(content, metadata);
	}

View on GitHub (pinned to 98a7beda4f)