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
- Serve the media from a public http/https URL that passes strict validation and is reachable from the client machine.
- Prefer base64-encoded data: pass the raw base64 string instead of a URL so no fetch occurs.
- If the URL is intentionally local (loopback/IMDS), the strict validator/SSRF guard will block it — embed the bytes instead.
- 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
- Prefer base64/byte[] media over remote URLs for local or internal assets
- Never point media URLs at localhost, 169.254.169.254, or private ranges
- Curl the URL first in CI to verify reachability
- Check e.getCause() to distinguish fetch failure from policy rejection
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
- URL is not valid under strict validation rules:
- Unsupported URL protocol:
- Host '' is not in the allowed hosts list. Configure MediaFet
- Media URL response exceeds maximum allowed size of bytes:
- Media URL response exceeds maximum allowed size of bytes
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/2ec63f1df1a9407b.
Report an issue: GitHub.