{"record":{"id":"204e2905bc9902d5","repo":"chinabugotech/hutool","slug":"failed-to-get-remote-file-mime-type","errorCode":null,"errorMessage":"Failed to get remote file MIME type","messagePattern":"Failed to get remote file MIME type","errorType":"exception","errorClass":"AIException","httpStatus":null,"severity":"error","filePath":"hutool-ai/src/main/java/cn/hutool/ai/model/gemini/GeminiServiceImpl.java","lineNumber":452,"sourceCode":"\tprivate String getRemoteFileMimeType(String fileUri) {\n\t\ttry {\n\t\t\tfinal HttpRequest httpRequest = HttpRequest.get(fileUri)\n\t\t\t\t.header(Header.ACCEPT, \"application/json\")\n\t\t\t\t.header(\"x-goog-api-key\", config.getApiKey())\n\t\t\t\t.timeout(config.getTimeout());\n\t\t\tif (config.getHasProxy()) {\n\t\t\t\thttpRequest.setProxy(config.getProxy());\n\t\t\t}\n\t\t\tString responseBody = httpRequest.execute().body();\n\t\t\tfinal JSONObject json = JSONUtil.parseObj(responseBody);\n\n\t\t\t//提取服务端的mimeType\n\t\t\tString mimeType = json.getStr(\"mimeType\");\n\t\t\tif (StrUtil.isNotBlank(mimeType)) {\n\t\t\t\treturn mimeType;\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\tthrow new AIException(\"Failed to get remote file MIME type\", e.getMessage());\n\t\t}\n\t\treturn \"application/octet-stream\";\n\t}\n\n\t/**\n\t * 发送Get请求\n\t * @param endpoint 请求节点\n\t * @return 请求响应\n\t */\n\t@Override\n\tprotected HttpResponse sendGet(String endpoint) {\n\t\t//链式构建请求\n\t\ttry {\n\t\t\t//设置超时3分钟\n\t\t\tfinal HttpRequest httpRequest = HttpRequest.get(config.getApiUrl() + endpoint)\n\t\t\t\t.header(Header.ACCEPT, \"application/json\")\n\t\t\t\t.header(\"x-goog-api-key\", config.getApiKey())\n\t\t\t\t.timeout(config.getTimeout());","sourceCodeStart":434,"sourceCodeEnd":470,"githubUrl":"https://github.com/chinabugotech/hutool/blob/8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442/hutool-ai/src/main/java/cn/hutool/ai/model/gemini/GeminiServiceImpl.java#L434-L470","documentation":"Thrown by GeminiServiceImpl.getRemoteFileMimeType when fetching metadata for a Gemini files/ resource fails. SAME library bug as error 11: it calls new AIException(\"Failed to get remote file MIME type\", e.getMessage()), which resolves to the (String, Object... params) overload, so the underlying exception e is dropped and the real cause is lost from the stack trace. Triggered indirectly whenever a multimodal call references a files/ URI.","triggerScenarios":"A multimodal call passes a media string containing 'files/' whose URI is invalid, expired, belongs to another key/project, the apiKey (x-goog-api-key) is invalid/revoked, the metadata response is not valid JSON, or the network request fails.","commonSituations":"Uploaded file URI expired (Gemini files are temporary); key mismatch between upload and chat; a manually-constructed files/ URI that does not exist; reverse-proxy mangling the metadata endpoint; JSON parse failure when the endpoint returns an HTML error page.","solutions":["Re-upload the file to obtain a fresh, valid files/ URI and use it promptly.","Verify the apiKey is the same one used to upload and still valid.","Since the cause is lost (library bug), call the metadata GET manually to see the real status/body.","Confirm the files/ URI string is well-formed (starts with files/ or a full https URI)."],"exampleFix":"// before\nString r = gemini.chat(\"describe\", List.of(\"files/abc-123-old\"));\n// -> Failed to get remote file MIME type (cause lost)\n\n// after -- re-upload and use a fresh uri\nString up = gemini.uploadFile(myFile);\nString uri = JSONUtil.parseObj(up).getByPath(\"file.uri\", String.class);\nString r = gemini.chat(\"describe\", List.of(uri));","handlingStrategy":"validation","validationCode":"// Validate the files/ URI + key freshness before the multimodal call\nif (StrUtil.isBlank(media) || !media.contains(\"files/\")) {\n    throw new IllegalArgumentException(\"not a files/ URI: \" + media);\n}\nif (StrUtil.isBlank(config.getApiKey())) throw new IllegalStateException(\"key missing\");","typeGuard":"static boolean isGeminiFileUri(String m) {\n    return m != null && m.contains(\"files/\");\n}","tryCatchPattern":"try {\n    return gemini.chat(prompt, List.of(fileUri));\n} catch (AIException e) {\n    if (\"Failed to get remote file MIME type\".equals(e.getMessage())) {\n        // cause is LOST (varargs-overload bug); GET the metadata manually to see status/body\n    }\n    throw e;\n}","preventionTips":["Re-upload to get a fresh files/ URI; they are temporary.","Use the same key for upload and chat.","Because the cause is dropped, reproduce the metadata GET in isolation to debug."],"tags":["gemini","multimodal","network","error-masking","library-bug"],"backgroundTag":null,"analyzedSha":"8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442","analyzedAt":"2026-08-14T04:01:12.892Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}