{"record":{"id":"a54f1c69736ef322","repo":"spring-projects/spring-ai","slug":"ioexception-wrapped-runtimeexception-a54f1c","errorCode":null,"errorMessage":"IOException (wrapped RuntimeException)","messagePattern":"IOException \\(wrapped RuntimeException\\)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-commons/src/main/java/org/springframework/ai/reader/TextReader.java","lineNumber":100,"sourceCode":"\tpublic Map<String, Object> getCustomMetadata() {\n\t\treturn this.customMetadata;\n\t}\n\n\t@Override\n\tpublic List<Document> get() {\n\t\ttry (InputStream inputStream = this.resource.getInputStream()) {\n\n\t\t\tString document = StreamUtils.copyToString(inputStream, this.charset);\n\n\t\t\t// Inject source information as a metadata.\n\t\t\tthis.customMetadata.put(CHARSET_METADATA, this.charset.name());\n\t\t\tthis.customMetadata.put(SOURCE_METADATA, getResourceIdentifier(this.resource));\n\n\t\t\treturn List.of(new Document(document, this.customMetadata));\n\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n\n\tprotected String getResourceIdentifier(Resource resource) {\n\t\t// Try to get the filename first\n\t\tString filename = resource.getFilename();\n\t\tif (filename != null && !filename.isEmpty()) {\n\t\t\treturn filename;\n\t\t}\n\n\t\t// Try to get the URI\n\t\ttry {\n\t\t\tURI uri = resource.getURI();\n\t\t\treturn uri.toString();\n\t\t}\n\t\tcatch (IOException ignored) {\n\t\t\t// If getURI() throws an exception, we'll try the next method\n\t\t}","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-commons/src/main/java/org/springframework/ai/reader/TextReader.java#L82-L118","documentation":"TextReader.get() reads the resource's full text into a single Document; an IOException from the underlying stream is wrapped in a plain RuntimeException. The library treats an unreadable text source as fatal since producing the Document is the reader's whole purpose.","triggerScenarios":"Calling textReader.get() where resource.getInputStream() throws — missing file, no read permission, character-stream decode failure, or remote resource I/O error.","commonSituations":"Wrong file path or classpath location, files deleted between listing and reading, unreadable encodings, or network-backed resources dropping connections.","solutions":["Check resource.exists() and resource.isReadable() before creating/reading","Read bytes yourself with explicit IOException handling and wrap in a ByteArrayResource","Catch RuntimeException and inspect getCause() for the root IOException","Confirm charset assumptions; pass an explicit Charset to the TextReader constructor if needed"],"exampleFix":"// before\nTextReader reader = new TextReader(new FileSystemResource(path));\nDocument doc = reader.get().get(0);\n// after\nResource res = new FileSystemResource(path);\nAssert.isTrue(res.exists() && res.isReadable(), \"Cannot read text: \" + path);\nTextReader reader = new TextReader(res);\nDocument doc = reader.get().get(0);","handlingStrategy":"validation","validationCode":"Assert.isTrue(resource.exists() && resource.isReadable(), \"Cannot read text resource: \" + resource.getDescription());","typeGuard":null,"tryCatchPattern":"try {\n    List<Document> docs = textReader.get();\n} catch (RuntimeException e) {\n    throw new IllegalStateException(\"Text read failed: \" + resource, e.getCause());\n}","preventionTips":["Check exists()/isReadable() before constructing TextReader","Copy remote/unstable sources to local temp files first","Specify an explicit Charset when the source encoding is uncertain"],"tags":["io","text","reader"],"backgroundTag":"file-read-failed","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}