chinabugotech/hutool · error · AIException

Failed to download media from URL:

Error message

Failed to download media from URL: 

What it means

Thrown inside GeminiServiceImpl.buildMultimodalRequestMap when HttpUtil.downloadBytes(media) fails for an http(s) media URL. IMPORTANT library bug: it is constructed as new AIException("..." + media, e.getMessage()). Because e.getMessage() is a String (an Object, not a Throwable), Java resolves this to the (String messageTemplate, Object... params) overload, NOT (String, Throwable). The original exception e is therefore NOT attached as a cause and the root stack trace is lost, which makes debugging hard.

Source

Thrown at hutool-ai/src/main/java/cn/hutool/ai/model/gemini/GeminiServiceImpl.java:332

					));
				} else if (media.startsWith("http")) {
					//普通网络图片 (下载并转 Base64)
					try {
						final byte[] bytes = HttpUtil.downloadBytes(media);
						//尝试识别下载文件的 MIME,无法识别则不强加后缀逻辑,通过流内容自适应
						String mime = FileUtil.getMimeType(media);
						if (StrUtil.isBlank(mime)) {
							// 基础兜底
							mime = "image/jpeg";
						}
						parts.add(MapUtil.ofEntries(
							MapUtil.entry("inline_data", MapUtil.ofEntries(
								MapUtil.entry("mime_type", mime),
								MapUtil.entry("data", Base64.encode(bytes))
							))
						));
					} catch (Exception e) {
						throw new AIException("Failed to download media from URL: " + media, e.getMessage());
					}
				} else {
					//Base64 数据
					parts.add(MapUtil.ofEntries(
						MapUtil.entry("inline_data", MapUtil.ofEntries(
							MapUtil.entry("mime_type", "image/jpeg"),
							MapUtil.entry("data", media)
						))
					));
				}
			}
		}

		final Map<String, Object> paramMap = new HashMap<>();
		paramMap.put("contents", Collections.singletonList(MapUtil.ofEntries(
			MapUtil.entry("role", "user"),
			MapUtil.entry("parts", parts)
		)));

View on GitHub (pinned to 8870454b2a)

Solutions

  1. Pre-download and validate the media yourself before the multimodal call (e.g. HttpUtil.downloadBytes then check length).
  2. Ensure the URL is reachable from the JVM host and returns binary content.
  3. For authed/private media, download with proper headers and pass the bytes as base64 inline data instead.
  4. Since the cause is lost (library bug), reproduce the download in isolation to see the real IOException.

Example fix

// before
String r = gemini.chat("describe this", List.of("https://broken.example/img.png"));
// -> Failed to download media from URL: ... (cause lost)

// after -- validate first, pass base64 on failure
byte[] bytes;
try { bytes = HttpUtil.downloadBytes(url); }
catch (Exception ex) { throw new IOException("cannot fetch " + url, ex); }
String b64 = media.startsWith("http") ? Base64.encode(bytes) : media;
String r = gemini.chat("describe this", List.of(b64));
Defensive patterns

Strategy: validation

Validate before calling

// Pre-download and validate media; pass base64 on success
byte[] bytes;
try {
    bytes = HttpUtil.downloadBytes(mediaUrl);
} catch (Exception ex) {
    throw new IllegalStateException("cannot fetch media " + mediaUrl, ex);
}
if (bytes == null || bytes.length == 0) throw new IllegalStateException("empty media");
String b64 = cn.hutool.core.codec.Base64.encode(bytes);
gemini.chat(prompt, List.of(b64));

Type guard

static boolean isHttpMedia(String m) {
    return m != null && (m.startsWith("http://") || m.startsWith("https://"));
}

Try / catch

try {
    return gemini.chat(prompt, mediaList);
} catch (AIException e) {
    if (e.getMessage().startsWith("Failed to download media from URL")) {
        // NOTE: cause is LOST (varargs-overload bug) -- reproduce the download
        // separately to see the real IOException.
    }
    throw e;
}

Prevention

When it happens

Trigger: Passing an http(s) image/media URL (not a Gemini files/ URI, not raw base64) to a multimodal call where the URL is unreachable, returns 404, has an invalid certificate, requires authentication, redirects too many times, or times out during download.

Common situations: Image host behind auth/CDN that needs headers hutool does not send; intranet URL not reachable from the server; typo in the URL; the media host returns HTML instead of binary; large image exceeding download timeout.

Related errors


AI-assisted analysis of chinabugotech/hutool@8870454b2a (2026-08-14). Data as JSON: /api/errors/a02143543b0edd3a. Report an issue: GitHub.