spring-projects/spring-ai · error · RuntimeException

java.net.URISyntaxException

Error message

java.net.URISyntaxException

What it means

DefaultChatClient's PromptUserSpec.media(MimeType, URL) converts the URL to a URI and wraps any URISyntaxException in a RuntimeException. A URL that is not a valid absolute, structured URI (e.g. has illegal characters or spaces) cannot be converted, so media registration fails.

Source

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

		private @Nullable String text;

		@Override
		public PromptUserSpec media(Media... media) {
			Assert.notNull(media, "media cannot be null");
			Assert.noNullElements(media, "media cannot contain null elements");
			this.media.addAll(Arrays.asList(media));
			return this;
		}

		@Override
		public PromptUserSpec media(MimeType mimeType, URL url) {
			Assert.notNull(mimeType, "mimeType cannot be null");
			Assert.notNull(url, "url cannot be null");
			try {
				this.media.add(Media.builder().mimeType(mimeType).data(url.toURI()).build());
			}
			catch (URISyntaxException e) {
				throw new RuntimeException(e);
			}
			return this;
		}

		@Override
		public PromptUserSpec media(MimeType mimeType, Resource resource) {
			Assert.notNull(mimeType, "mimeType cannot be null");
			Assert.notNull(resource, "resource cannot be null");
			this.media.add(Media.builder().mimeType(mimeType).data(resource).build());
			return this;
		}

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

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. URL-encode the string before constructing the URL (URLEncoder / URI multi-arg constructor)
  2. Build via new URI(scheme, authority, path, query, fragment) which escapes components
  3. Validate the URL format before passing it to media()
  4. Use the media(MimeType, Resource) overload with a proper Resource instead of a URL

Example fix

// before
.user(u -> u.media(mimeType, new URL("https://x.com/my file.png"))) // URISyntaxException
// after
URI uri = new URI("https", "x.com", "/my file.png", null);
.user(u -> u.media(mimeType, uri.toURL()));
Defensive patterns

Strategy: try-catch

Validate before calling

try { new URI(url.toString()); } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid media URL: " + url, e); }

Type guard

static Optional<URI> safeUri(URL url) { try { return Optional.of(url.toURI()); } catch (URISyntaxException e) { return Optional.empty(); } }

Try / catch

try { builder.user(u -> u.media(mime, url)); } catch (RuntimeException e) { if (e.getCause() instanceof URISyntaxException) { /* encode URL and retry */ } throw e; }

Prevention

When it happens

Trigger: Calling .user(u -> u.media(mimeType, new URL("..."))) with a URL containing unescaped spaces/illegal characters, a relative URL like new URL("foo/bar"), or a malformed protocol string passed to the URL constructor.

Common situations: Interpolating unencoded file names or query strings into the URL; using file: URLs built by string concatenation; URLs read from user input or config that were never encoded.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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