spring-projects/spring-ai · error · RuntimeException

java.io.IOException

Error message

java.io.IOException

What it means

PromptUserSpec.text(Resource, Charset) reads the Resource content via getContentAsString, which can throw IOException; the client wraps it in a RuntimeException. It indicates the underlying resource (file, classpath entry, URL resource) could not be read.

Source

Thrown at spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java:204

			return this;
		}

		@Override
		public PromptUserSpec text(String text) {
			Assert.hasText(text, "text cannot be null or empty");
			this.text = text;
			return this;
		}

		@Override
		public PromptUserSpec text(Resource text, Charset charset) {
			Assert.notNull(text, "text cannot be null");
			Assert.notNull(charset, "charset cannot be null");
			try {
				this.text(text.getContentAsString(charset));
			}
			catch (IOException e) {
				throw new RuntimeException(e);
			}
			return this;
		}

		@Override
		public PromptUserSpec text(Resource text) {
			Assert.notNull(text, "text cannot be null");
			this.text(text, Charset.defaultCharset());
			return this;
		}

		@Override
		public PromptUserSpec param(String key, Object value) {
			Assert.hasText(key, "key cannot be null or empty");
			Assert.notNull(value, "value cannot be null");
			this.params.put(key, value);
			return this;
		}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Verify the resource exists (resource.exists()) and is readable before passing it
  2. Check the classpath (src/main/resources) or absolute file path correctness
  3. Read the content yourself with proper error handling and pass the String via text(String)
  4. Catch the RuntimeException and inspect the IOException cause for the real path/message

Example fix

// before
.user(u -> u.text(new FileSystemResource("prompts/user.txt"), StandardCharsets.UTF_8)); // missing file
// after
Resource res = new FileSystemResource("prompts/user.txt");
if (res.exists()) {
    .user(u -> u.text(res, StandardCharsets.UTF_8));
} else {
    throw new IllegalStateException("Prompt resource missing: " + res.getFilename());
}
Defensive patterns

Strategy: validation

Validate before calling

Resource r = new ClassPathResource("prompts/user.txt"); if (!r.exists() || !r.isReadable()) throw new IllegalStateException("User prompt resource missing/unreadable");

Type guard

static String safeText(Resource r, Charset cs) { try { return r.getContentAsString(cs); } catch (IOException e) { return null; } }

Try / catch

try { builder.user(u -> u.text(res, charset)); } catch (RuntimeException e) { if (e.getCause() instanceof IOException io) { throw new IllegalStateException("Cannot read user prompt: " + io.getMessage(), io); } throw e; }

Prevention

When it happens

Trigger: Calling .user(u -> u.text(resource, charset)) where the Resource points to a missing file, unreadable location, or a stream that fails mid-read (e.g. FileSystemResource on a non-existent path, ClassPathResource not on the classpath).

Common situations: Typo'd classpath prompt file; file deleted or path relative to wrong working directory; permissions issue on a mounted volume; encoding mismatch causing read failure.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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