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

Too much redirects

Error message

Too much redirects

What it means

FetchTask.downloadHttp() follows HTTP 3xx redirects manually and caps the chain at 20 hops to prevent infinite redirect loops. This IOException is thrown when the server has redirected more than 20 times, indicating a redirect loop or pathological configuration.

Solutions

  1. Open the URL in a browser or with curl -IL to inspect the redirect chain and find the loop
  2. Fix the target server/proxy configuration causing the loop, or use a direct final URL
  3. Authenticate properly if redirects are caused by missing cookies/credentials
  4. Clear any cached stale URL and use the canonical endpoint
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check redirect chain before downloading
HttpURLConnection c = (HttpURLConnection) uri.toURL().openConnection();
c.setInstanceFollowRedirects(false);
int hops = 0;
while (c.getResponseCode() >= 300 && c.getResponseCode() <= 308 && ++hops <= 25)
    c = (HttpURLConnection) new URI(c.getHeaderField("Location")).toURL().openConnection();
if (hops > 20) throw new IllegalStateException("Redirect loop at " + uri);

Try / catch

try {
    fetchTask.run();
} catch (IOException e) {
    if (e.getMessage().equals("Too much redirects")) useDirectUrlOrFallbackMirror();
    else throw e;
}

Prevention

When it happens

Trigger: A URL whose redirect chain exceeds 20 hops — typically an infinite redirect loop (A -> B -> A), a misconfigured CDN, or cookie/auth issues causing repeated redirects to login and back.

Common situations: Mirror servers misconfigured with redirect loops; auth-gated endpoints bouncing between login and target; reverse proxies with conflicting rewrite rules.

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/912e24be123c4dfb. Report an issue: GitHub.

Appendix: source

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

                            headers.forEach(connection::setRequestProperty);
                            responseCode = connection.getResponseCode();
                            responseInfo = UrlResponseInfo.of(connection);

                            bmclapiHash = responseInfo.headers().firstValue("x-bmclapi-hash").orElse(null);
                            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 {

View on GitHub (pinned to 24702dc5a0)