spring-projects/spring-ai · error · RuntimeException

java.io.IOException

Error message

java.io.IOException

What it means

Builder.defaultUser(Resource, Charset) reads the resource text with getContentAsString(charset); an IOException (unreadable/missing resource) is wrapped and rethrown as a plain RuntimeException. The wrapping hides the underlying cause from the top-level message.

Source

Thrown at spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java:154

	public Builder defaultOptions(ChatOptions.Builder customizer) {
		this.defaultRequest.options(customizer);
		return this;
	}

	public Builder defaultUser(String text) {
		this.defaultRequest.user(text);
		return this;
	}

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

	public Builder defaultUser(Resource text) {
		return this.defaultUser(text, Charset.defaultCharset());
	}

	public Builder defaultUser(Consumer<PromptUserSpec> userSpecConsumer) {
		this.defaultRequest.user(userSpecConsumer);
		return this;
	}

	public Builder defaultSystem(String text) {
		this.defaultRequest.system(text);
		return this;
	}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Check that the Resource exists and is readable before calling defaultUser (resource.exists()).
  2. Read e.getCause() to see the actual IOException and fix the path/stream.
  3. Fall back to defaultUser(String) with content loaded and validated by your own code.
  4. Ensure the resource is on the runtime classpath of the deployed artifact.

Example fix

// before
builder.defaultUser(new ClassPathResource("prompts/user.txt"));
// after
Resource r = new ClassPathResource("prompts/user.txt");
if (!r.exists()) throw new IllegalStateException("user prompt resource missing");
builder.defaultUser(r);
Defensive patterns

Strategy: validation

Validate before calling

Resource r = new ClassPathResource("prompts/user.txt");
if (!r.exists()) throw new IllegalStateException("missing default user resource");
builder.defaultUser(r);

Try / catch

try { builder.defaultUser(resource); } catch (RuntimeException e) { log.error("defaultUser resource read failed", e.getCause()); }

Prevention

When it happens

Trigger: ChatClient.builder().defaultUser(Resource text, Charset charset) (or defaultUser(Resource)) where the Resource cannot be read: nonexistent classpath/file resource, closed stream, or I/O failure.

Common situations: Building a shared ChatClient bean with a default user prompt loaded from a resource that wasn't packaged or whose path changed; charset issues with malformed input streams.

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