spring-projects/spring-ai · error · RuntimeException

Failed to read media data from URL:

Error message

Failed to read media data from URL: 

What it means

BedrockProxyChatModel.mapMediaToContentBlock wraps SecurityException and RestClientException from fetching a String-typed media URL into this RuntimeException. It means the library attempted an HTTP fetch of the URL passed as Media data and either the fetch failed (network/HTTP error) or a security check rejected it. The failing URL is appended to the message.

Source

Thrown at models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java:546

		else if (BedrockMediaFormat.isSupportedImageFormat(mimeType)) { // Image
			ImageSource.Builder sourceBuilder = ImageSource.builder();
			if (media.getData() instanceof byte[] bytes) {
				sourceBuilder.bytes(SdkBytes.fromByteArrayUnsafe(bytes)).build();
			}
			else if (media.getData() instanceof String text) {

				if (text.startsWith("s3://")) {
					sourceBuilder.s3Location(S3Location.builder().uri(text).build()).build();
				}
				else if (text.startsWith("http://") || text.startsWith("https://")) {
					// Not base64
					if (URLValidator.isValidURLStrict(text)) {
						try {
							byte[] bytes = this.mediaFetcher.fetch(URI.create(text));
							sourceBuilder.bytes(SdkBytes.fromByteArrayUnsafe(bytes)).build();
						}
						catch (SecurityException | RestClientException e) {
							throw new RuntimeException("Failed to read media data from URL: " + text, e);
						}
					}
					else {
						throw new SecurityException("URL is not valid under strict validation rules: " + text);
					}
				}
				else {
					// Assume it's base64-encoded image data
					sourceBuilder.bytes(SdkBytes.fromByteArray(Base64.getDecoder().decode(text)));
				}
			}
			else if (media.getData() instanceof URL url) {

				try {
					String protocol = url.getProtocol();
					if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) {
						throw new SecurityException("Unsupported URL protocol: " + protocol);
					}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Serve the media from a public http/https URL that passes strict validation and is reachable from the client machine.
  2. Prefer base64-encoded data: pass the raw base64 string instead of a URL so no fetch occurs.
  3. If the URL is intentionally local (loopback/IMDS), the strict validator/SSRF guard will block it — embed the bytes instead.
  4. Wrap the call in try-catch for RuntimeException with cause SecurityException|RestClientException to surface the real cause.

Example fix

// before
Media media = new Media(MimeTypeUtils.IMAGE_PNG, "http://localhost:8080/cat.png");
// after
byte[] bytes = Files.readAllBytes(Path.of("cat.png"));
Media media = new Media(MimeTypeUtils.IMAGE_PNG,
    new Media.DataObject(Base64.getEncoder().encodeToString(bytes)));
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate before sending
if (URLValidator.isValidURLStrict(urlString) == false || !(urlString.startsWith("http://") || urlString.startsWith("https://"))) {
    throw new IllegalArgumentException("Media URL not allowed: " + urlString);
}

Type guard

boolean isFetchableHttpUrl(String s) {
    return s != null && (s.startsWith("http://") || s.startsWith("https://")) && URLValidator.isValidURLStrict(s);
}

Try / catch

try {
    model.call(prompt);
} catch (RuntimeException e) {
    if (e.getCause() instanceof SecurityException || e.getCause() instanceof RestClientException) {
        // fall back to base64-embedded media
    } else throw e;
}

Prevention

When it happens

Trigger: Passing a Media whose data is a String that passes strict URL validation (URLValidator.isValidURLStrict) but whose fetch via mediaFetcher.fetch(URI) throws RestClientException (connection refused, DNS failure, non-2xx, timeout), or an inner SecurityException (e.g. blocked scheme or SSRF-guard failure) propagating to the catch.

Common situations: Developer passes 'http://localhost:8080/img.png' or an AWS IMDS URL ('http://169.254.169.254/...') as image data; SSRF protections block loopback/link-local hosts. Also common: unreachable hosts, self-signed TLS, or a URL that resolves but returns 404/403 from the media server.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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