HMCL-dev/HMCL · error · java.io.IOException

Redirected to an empty location

Error message

Redirected to an empty location

What it means

When FetchTask.downloadHttp() receives a redirect response, it reads the 'Location' header to build the next URI. This IOException is thrown when the Location header is missing or blank, so the redirect target cannot be determined.

Solutions

  1. Fix or bypass the misconfigured server sending redirects without Location
  2. Use the final direct URL instead of the redirecting endpoint
  3. Test with curl -I to confirm the malformed response before contacting the server admin
  4. Catch the IOException and fall back to an alternative mirror
Defensive patterns

Strategy: fallback

Validate before calling

HttpURLConnection c = (HttpURLConnection) uri.toURL().openConnection();
c.setInstanceFollowRedirects(false);
if ((c.getResponseCode() / 100) == 2 && c.getHeaderField("Location") != null)
    throw new IllegalStateException("Malformed redirect (no Location) from " + uri.getHost());

Try / catch

try {
    fetchTask.run();
} catch (IOException e) {
    if (e.getMessage().contains("empty location")) failoverToMirror(uri);
    else throw e;
}

Prevention

When it happens

Trigger: A server returns 3xx (301/302/307/308) without a valid Location header — a malformed redirect from a misconfigured server, proxy, or load balancer.

Common situations: Buggy reverse proxies or health-check endpoints responding with redirect codes but no Location; custom server implementations that set status codes without headers.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java:357

                            if (DigestUtils.isSha1Digest(bmclapiHash)) {
                                Optional<Path> cache = repository.checkExistentFile(null, "SHA-1", bmclapiHash);
                                if (cache.isPresent()) {
                                    useCachedResult(cache.get());
                                    LOG.info("Using cached file for " + NetworkUtils.dropQuery(uri));
                                    return;
                                }
                            }

                            if (responseCode >= 300 && responseCode <= 308 && responseCode != 306 && responseCode != 304) {
                                if (redirects == null) {
                                    redirects = new ArrayList<>();
                                } else if (redirects.size() >= 20) {
                                    throw new IOException("Too much redirects");
                                }

                                String location = connection.getHeaderField("Location");
                                if (StringUtils.isBlank(location))
                                    throw new IOException("Redirected to an empty location");

                                URI target = currentURI.resolve(NetworkUtils.encodeLocation(location));
                                redirects.add(target);

                                if (!NetworkUtils.isHttpUri(target))
                                    throw new IOException("Redirected to not http URI: " + target);

                                currentURI = target;
                            } else {
                                keepConnection = true;
                                break;
                            }
                        } finally {
                            if (!keepConnection && connection != null) {
                                closeHttpConnection(connection);
                                connection = null;
                            }
                        }

View on GitHub (pinned to 24702dc5a0)