conductor-oss/conductor · error · RuntimeException

Empty response downloading from

Error message

Empty response downloading from {url}

What it means

GeminiVertex.downloadFromUrl() throws this when the OkHttp response body is null after downloading from a URL returned by the Gemini API for generated media (video, audio). A null body means the server responded but sent no content — typically because the signed URL has expired or the resource is no longer available. RuntimeException is then rethrown unchanged (the second catch block passes it through).

Solutions

  1. Download media immediately after the video/audio generation job completes — do not store the URL for later retrieval.
  2. If the URL has expired, re-submit the generation job to get a fresh URL.
  3. Cache the downloaded bytes (Conductor's checkVideoStatus already downloads and stores bytes), so repeated access doesn't re-fetch from the stale URL.
  4. Increase the OkHttp timeout to ensure large media downloads complete before the URL expires.

Example fix

// before — download later, URL may expire
String videoUrl = response.getResults().get(0).getUrl();
// ... store url, download hours later
byte[] bytes = downloadFromUrl(videoUrl); // null body

// after — download immediately when status is COMPLETED
if ("COMPLETED".equals(status)) {
    byte[] bytes = downloadFromUrl(video.getUrl());
    mediaList.add(Media.builder().data(bytes).mimeType(mime).build());
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the media URL before downloading
void validateMediaUrl(String url) {
    if (url == null || url.isBlank()) {
        throw new IllegalArgumentException("Media URL is null or empty");
    }
    // Check if this is likely a signed URL that may have expired
    if (url.contains("Expires=")) {
        long expires = extractExpiry(url);
        if (expires < System.currentTimeMillis() / 1000) {
            throw new IllegalStateException(
                "Media URL has expired — re-generate to get a fresh URL");
        }
    }
}

Try / catch

try {
    byte[] media = vertex.downloadFromUrl(url);
} catch (RuntimeException e) {
    if (e.getMessage().startsWith("Empty response downloading from")) {
        throw new IllegalStateException(
            "Media URL returned empty response — likely expired. "
            + "Re-generate the video/audio to get a fresh URL.", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: The URL returned by Gemini for a generated video or audio resource returns an HTTP response with a null body. This happens when: the signed URL has expired (Google signed URLs have a limited TTL), the resource was garbage-collected after a delay between generation and download, or the CDN returned an empty response.

Common situations: A delay between video generation completion (checkVideoStatus returns COMPLETED) and the actual download of the media. The signed URL TTL expired during processing. The job was from a previous session and the URL is stale. Conductor restarted and is reprocessing old results.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/1da26211112eb875. Report an issue: GitHub.

Appendix: source

Thrown at ai/src/main/java/org/conductoross/conductor/ai/providers/gemini/GeminiVertex.java:193

                                    .data(Base64.getDecoder().decode(video.getB64Json()))
                                    .mimeType(mimeType)
                                    .build());
                } else if (video.getUrl() != null) {
                    byte[] bytes = downloadFromUrl(video.getUrl());
                    mediaList.add(Media.builder().data(bytes).mimeType(mimeType).build());
                }
            }
            builder.media(mediaList);
        }

        return builder.build();
    }

    private byte[] downloadFromUrl(String url) {
        okhttp3.Request request = new okhttp3.Request.Builder().url(url).get().build();
        try (okhttp3.Response response = httpClient.newCall(request).execute()) {
            if (response.body() == null) {
                throw new RuntimeException("Empty response downloading from " + url);
            }
            return response.body().bytes();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("Failed to download from " + url, e);
        }
    }

    @Override
    public LLMResponse generateAudio(AudioGenRequest request) {
        GeminiApi.SpeechConfig speechConfig =
                new GeminiApi.SpeechConfig(
                        new GeminiApi.VoiceConfig(
                                new GeminiApi.PrebuiltVoiceConfig(request.getVoice())));
        GeminiApi.GenerationConfig genConfig =
                new GeminiApi.GenerationConfig(
                        null,

View on GitHub (pinned to cf7c3e4a8a)