apache/druid · error · IllegalArgumentException
Invalid maxAttempts[ ] in retry policy
Error message
Invalid maxAttempts[%d] in retry policy
What it means
ServiceClientImpl's constructor rejects a ServiceRetryPolicy whose maxAttempts is 0, since a client that never attempts any request is useless. maxAttempts must be at least 1; anything else throws an IllegalArgumentException.
Solutions
- Set retryPolicy.maxAttempts() to at least 1 (e.g., ServiceRetryPolicy.basic() or maxAttempts(3)).
- If 0 means 'no retries', convert it: maxAttempts = retries + 1.
- Add validation at config load time so 0 is rejected or coerced before reaching ServiceClientImpl.
Example fix
// before ServiceRetryPolicy policy = new ServiceRetryPolicy(0, backoffFactory); // after ServiceRetryPolicy policy = new ServiceRetryPolicy(3, backoffFactory); // or retries + 1
Defensive patterns
Strategy: validation
Validate before calling
if (retryPolicy.maxAttempts() < 1) {
retryPolicy = new ServiceRetryPolicy(1, retryPolicy.backoffFactory()); // or retries + 1
} Try / catch
try { return new ServiceClientImpl(httpClient, locator, retryPolicy, connectExec); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("Invalid maxAttempts")) { return new ServiceClientImpl(httpClient, locator, retryPolicy.withMaxAttempts(3), connectExec); } throw e; } Prevention
- Remember maxAttempts counts total attempts, not retries
- Coerce configured retry counts: maxAttempts = configuredRetries + 1
- Validate retry policy numbers when parsing config
- Prefer ServiceRetryPolicy.basic() as a sane default
When it happens
Trigger: Building a ServiceClientImpl with a ServiceRetryPolicy built as maxAttempts(0), often from config or a computation that produced 0 (e.g., default minus one, unset counter).
Common situations: Config parsing yielding 0 retries and being passed directly as maxAttempts; confusion between 'retry count' and 'attempt count' semantics when constructing the policy.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- At least one task runner must be enabled
- Cannot define both uri and fileRegex
- Cannot have fault tolerance without durable storage
- Cannot mix sortable and unsortable key columns
- Cannot specify both versionRegex and fileRegex…
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/a825f33d9530a070.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/main/java/org/apache/druid/rpc/ServiceClientImpl.java:81
// Populated when we receive a redirect. The location here has no base path; it only identifies a preferred server.
private final AtomicReference<ServiceLocation> preferredLocationNoPath = new AtomicReference<>();
public ServiceClientImpl(
final String serviceName,
final HttpClient httpClient,
final ServiceLocator serviceLocator,
final ServiceRetryPolicy retryPolicy,
final ScheduledExecutorService connectExec
)
{
this.serviceName = Preconditions.checkNotNull(serviceName, "serviceName");
this.httpClient = Preconditions.checkNotNull(httpClient, "httpClient");
this.serviceLocator = Preconditions.checkNotNull(serviceLocator, "serviceLocator");
this.retryPolicy = Preconditions.checkNotNull(retryPolicy, "retryPolicy");
this.connectExec = Preconditions.checkNotNull(connectExec, "connectExec");
if (retryPolicy.maxAttempts() == 0) {
throw new IAE("Invalid maxAttempts[%d] in retry policy", retryPolicy.maxAttempts());
}
}
@VisibleForTesting
public static long computeBackoffMs(final ServiceRetryPolicy retryPolicy, final long attemptNumber)
{
return Math.max(
retryPolicy.minWaitMillis(),
Math.min(retryPolicy.maxWaitMillis(), (long) (Math.pow(2, attemptNumber) * retryPolicy.minWaitMillis()))
);
}
@Override
public <IntermediateType, FinalType> ListenableFuture<FinalType> asyncRequest(
final RequestBuilder requestBuilder,
final HttpResponseHandler<IntermediateType, FinalType> handler
)
{View on GitHub (pinned to 9b90983fd2)