elastic/elasticsearch · error · IllegalArgumentException
baseBackoffMillis must be >= 0 but was [{}]
Error message
baseBackoffMillis must be >= 0 but was [{}] What it means
HttpUtils.readHttpBytesWithRetry validates baseBackoffMillis >= 0 because the backoff is computed as baseBackoffMillis * (attempt-1) and a negative base would produce nonsensical (negative) sleep durations. The guard surfaces the invalid value directly.
Source
Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/util/HttpUtils.java:36
private static final long HTTP_READ_RETRY_BACKOFF_MILLIS = 1000L;
private HttpUtils() {}
@FunctionalInterface
public interface Sleeper {
void sleep(long millis) throws InterruptedException;
}
public static byte[] readHttpBytesWithRetry(String url) throws IOException {
return readHttpBytesWithRetry(url, HTTP_READ_MAX_ATTEMPTS, HTTP_READ_RETRY_BACKOFF_MILLIS, Thread::sleep);
}
public static byte[] readHttpBytesWithRetry(String url, int maxAttempts, long baseBackoffMillis, Sleeper sleeper) throws IOException {
if (maxAttempts <= 0) {
throw new IllegalArgumentException("maxAttempts must be >= 1 but was [" + maxAttempts + "]");
}
if (baseBackoffMillis < 0) {
throw new IllegalArgumentException("baseBackoffMillis must be >= 0 but was [" + baseBackoffMillis + "]");
}
IOException lastException = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
if (attempt > 1 && baseBackoffMillis > 0) {
long backoff = baseBackoffMillis * (attempt - 1);
try {
sleeper.sleep(backoff);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while retrying download from: " + url, e);
}
}
try (InputStream in = URI.create(url).toURL().openStream()) {
return in.readAllBytes();
} catch (IOException e) {
lastException = e;View on GitHub (pinned to db6a809a66)
Solutions
- Pass baseBackoffMillis = 0 to disable backoff, or a positive value to enable it.
- Clamp configured values: Math.max(0, configuredBackoff) before calling.
- Audit the source of the negative value — it usually indicates a config-resolution bug.
Example fix
// before HttpUtils.readHttpBytesWithRetry(url, attempts, -100L, sleeper); // after HttpUtils.readHttpBytesWithRetry(url, attempts, 0L, sleeper); // 0 disables backoff
Defensive patterns
Strategy: validation
Validate before calling
long safeBackoff = Math.max(0, configuredBackoff); HttpUtils.readHttpBytesWithRetry(url, attempts, safeBackoff, sleeper);
Try / catch
try { HttpUtils.readHttpBytesWithRetry(url, attempts, backoff, sleeper); } catch (IllegalArgumentException e) { HttpUtils.readHttpBytesWithRetry(url); } Prevention
- Use 0 to disable backoff rather than negative sentinels.
- Validate backoff config at load time.
- Prefer the built-in defaults unless you have a measured reason to tune.
When it happens
Trigger: Calling readHttpBytesWithRetry with a negative baseBackoffMillis argument; a config typo or sign error feeding a negative long.
Common situations: Misconfigured retry backoff property; passing -1 as a 'disable backoff' sentinel instead of 0; arithmetic producing a negative value.
Related errors
- maxAttempts must be >= 1 but was [{}]
- classname is a required setting for esplugin
- classname is a forbidden for stable esplugin
- invalid deploymentTarget '{}', expected one of {}
- unrecognized classpath entry: {}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/b853f66f62c159a4.
Report an issue: GitHub.