HMCL-dev/HMCL · error · IOException

Missing download URI:

Error message

Missing download URI: 

What it means

The Disco download flow requires a direct download URI in the resolved package info. If DiscoRemoteFileInfo.directDownloadUri() is blank, the dialog throws IOException("Missing download URI: " + json) because it has nowhere to download the JDK archive from.

Solutions

  1. Retry with a different Java distribution or version in the dialog
  2. Inspect the JSON in the error message to confirm directDownloadUri is absent
  3. Download the JDK manually from the vendor site and add it via Java management
  4. Update HMCL in case the upstream API changed field names/semantics

Example fix

// before
if (StringUtils.isBlank(fileInfo.directDownloadUri()))
    throw new IOException("Missing download URI: " + json);
// after
if (StringUtils.isBlank(fileInfo.directDownloadUri()))
    throw new IOException("Missing download URI for " + version.getDistributionName()
        + "; try a different distribution or download manually");
Defensive patterns

Strategy: validation

Validate before calling

// before initiating the download task
JsonObject info = results.get(0).getAsJsonObject();
String uri = info.has("directDownloadUri") && !info.get("directDownloadUri").isJsonNull()
    ? info.get("directDownloadUri").getAsString() : null;
if (StringUtils.isBlank(uri)) {
    throw new IllegalStateException("Disco entry has no direct download URI; pick another distribution");
}

Try / catch

try {
    downloadJava(version);
} catch (IOException e) {
    if (e.getMessage().startsWith("Missing download URI:")) {
        openVendorDownloadPage(version.getDistributionName());
    } else throw e;
}

Prevention

When it happens

Trigger: Downloading Java via the Disco API when the returned package entry has an empty/absent directDownloadUri field — the API resolved the query but returned only metadata without a direct link.

Common situations: Distributions that only expose indirect download pages; upstream API response shape changes; network proxies/CDN stripping fields; requests for combinations where foojay has no direct artifact link.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/f9e0191e63f31c8f. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java:387

            DiscoJavaRemoteVersion version = remoteVersionBox.getSelectionModel().getSelectedItem();
            JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem();

            if (version == null)
                return;

            Controllers.taskDialog(new GetTask(downloadProvider.injectURLWithCandidates(version.getLinks().pkgInfoUri()))
                    .setExecutor(Schedulers.io())
                    .thenComposeAsync(json -> {
                        DiscoResult<DiscoRemoteFileInfo> result = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoRemoteFileInfo.class));
                        if (result.getResult().size() != 1)
                            throw new IOException("Illegal result: " + json);

                        DiscoRemoteFileInfo fileInfo = result.getResult().get(0);
                        if (StringUtils.isNotBlank(fileInfo.checksumType())
                                && !fileInfo.checksumType().equals("sha1") && !fileInfo.checksumType().equals("sha256") && !fileInfo.checksumType().equals("md5"))
                            throw new IOException("Unsupported checksum type: " + fileInfo.checksumType());
                        if (StringUtils.isBlank(fileInfo.directDownloadUri()))
                            throw new IOException("Missing download URI: " + json);

                        Path targetFile = Files.createTempFile("hmcl-java-", "." + version.getArchiveType());
                        targetFile.toFile().deleteOnExit();

                        Task<FileDownloadTask.IntegrityCheck> getIntegrityCheck;
                        if (StringUtils.isNotBlank(fileInfo.checksum()))
                            getIntegrityCheck = Task.completed(new FileDownloadTask.IntegrityCheck(fileInfo.checksumType(), fileInfo.checksum()));
                        else if (StringUtils.isNotBlank(fileInfo.checksumUri()))
                            getIntegrityCheck = new GetTask(downloadProvider.injectURLWithCandidates(fileInfo.checksumUri()))
                                    .thenApplyAsync(checksum -> {
                                        checksum = checksum.trim();

                                        int idx = checksum.indexOf(' ');
                                        if (idx > 0)
                                            checksum = checksum.substring(0, idx);

                                        return new FileDownloadTask.IntegrityCheck(fileInfo.checksumType(), checksum);
                                    });

View on GitHub (pinned to 24702dc5a0)