chinabugotech/hutool · error · AIException
Upload failed
Error message
Upload failed
What it means
Thrown by GeminiServiceImpl.uploadFile wrapping any exception during the two-step resumable upload (start request for the X-Goog-Upload-URL session URL, then PUT of the file bytes). The cause is preserved. Common underlying causes: the start request returned no X-Goog-Upload-URL header (auth failure or non-2xx), network failure on the PUT, file read error, or the upload base URL was computed incorrectly by getUploadBaseUrl.
Source
Thrown at hutool-ai/src/main/java/cn/hutool/ai/model/gemini/GeminiServiceImpl.java:198
.header("x-goog-api-key", config.getApiKey())
.header("X-Goog-Upload-Protocol", "resumable")
.header("X-Goog-Upload-Command", "start")
.header("X-Goog-Upload-Header-Content-Length", String.valueOf(file.length()))
.header("X-Goog-Upload-Header-Content-Type", mimeType)
.body(metadata).execute();
String sessionUrl = res.header("X-Goog-Upload-URL");
//上传二进制流
final HttpResponse uploadRes = HttpRequest.put(sessionUrl)
.header("X-Goog-Upload-Command", "upload, finalize")
.header("X-Goog-Upload-Offset", "0")
.body(FileUtil.readBytes(file)).execute();
//返回 JSON,调用者可以从中解析出 file.uri
return uploadRes.body();
} catch (Exception e) {
throw new AIException("Upload failed", e);
}
}
@Override
public byte[] addWavHeader(final byte[] pcmData) {
final int totalDataLen = pcmData.length;
final int totalAudioLen = totalDataLen + 36;
// Gemini TTS 默认通常是 24k 或 16k
final int sampleRate = 24000;
// 单声道
final int channels = 1;
// 16bit
final int byteRate = sampleRate * channels * 2;
final byte[] header = new byte[44];
header[0] = 'R'; header[1] = 'I'; header[2] = 'F'; header[3] = 'F';
header[4] = (byte) (totalAudioLen & 0xff);View on GitHub (pinned to 8870454b2a)
Solutions
- Inspect ex.getCause() for the real failure (IOException, IORuntimeException, or NPE from a null session URL).
- Verify the API key is valid and has file-upload scope.
- Confirm getUploadBaseUrl() returns the correct upload host for a custom/reverse-proxy apiUrl.
- Check the start response actually contains the X-Goog-Upload-URL header before issuing the PUT.
- Ensure the file is within Gemini's size limits (currently ~2GB per file) and the project quota is not exhausted.
Example fix
// before
gemini.uploadFile(hugeOrUnreadableFile);
// -> Upload failed (cause hidden behind generic message)
// after
try {
gemini.uploadFile(file);
} catch (AIException e) {
Throwable root = e.getCause() != null ? e.getCause() : e;
log.error("upload root cause: {}", root.toString());
// e.g. fix apiUrl via setApiUrl so getUploadBaseUrl() resolves correctly
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check key + size + session header presence before the PUT
if (StrUtil.isBlank(config.getApiKey())) throw new IllegalStateException("key missing");
if (file.length() > 2L * 1024 * 1024 * 1024) throw new IllegalStateException("file > 2GB"); Try / catch
try {
return gemini.uploadFile(file);
} catch (AIException e) {
Throwable root = e.getCause() != null ? e.getCause() : e;
// root often: IORuntimeException(start rejected), NPE(null X-Goog-Upload-URL),
// SocketException(PUT failed)
log.error("upload root: {}", root.toString(), root);
throw e;
} Prevention
- Inspect getCause() -- the message is generic.
- Verify the start response includes X-Goog-Upload-URL before the PUT.
- Confirm getUploadBaseUrl() is correct for custom/reverse-proxy apiUrl.
When it happens
Trigger: Invalid x-goog-api-key so the start request is rejected; the start response lacks X-Goog-Upload-URL (then HttpRequest.put(nullUrl) -> malformed URL / NPE); network error while streaming the bytes; FileUtil.readBytes fails (permissions); file exceeds the Gemini file size/quota limit; a custom reverse-proxy apiUrl that breaks getUploadBaseUrl derivation.
Common situations: Key rotated between sessions; very large file hitting quota; reverse-proxy that strips the X-Goog-Upload-URL response header; running against a Vertex-compatible endpoint where the upload host differs.
Related errors
- Failed to download media from URL:
- Failed to get remote file MIME type
- Failed to send GET request:
- Failed to send POST request:
- TTS处理失败:
AI-assisted analysis of chinabugotech/hutool@8870454b2a (2026-08-14).
Data as JSON: /api/errors/7d4c231307c0837f.
Report an issue: GitHub.