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

  1. Inspect ex.getCause() for the real failure (IOException, IORuntimeException, or NPE from a null session URL).
  2. Verify the API key is valid and has file-upload scope.
  3. Confirm getUploadBaseUrl() returns the correct upload host for a custom/reverse-proxy apiUrl.
  4. Check the start response actually contains the X-Goog-Upload-URL header before issuing the PUT.
  5. 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

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


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