{"record":{"id":"a825f33d9530a070","repo":"apache/druid","slug":"invalid-maxattempts-d-in-retry-policy","errorCode":null,"errorMessage":"Invalid maxAttempts[%d] in retry policy","messagePattern":"Invalid maxAttempts\\[(.+?)\\] in retry policy","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/org/apache/druid/rpc/ServiceClientImpl.java","lineNumber":81,"sourceCode":"  // Populated when we receive a redirect. The location here has no base path; it only identifies a preferred server.\n  private final AtomicReference<ServiceLocation> preferredLocationNoPath = new AtomicReference<>();\n\n  public ServiceClientImpl(\n      final String serviceName,\n      final HttpClient httpClient,\n      final ServiceLocator serviceLocator,\n      final ServiceRetryPolicy retryPolicy,\n      final ScheduledExecutorService connectExec\n  )\n  {\n    this.serviceName = Preconditions.checkNotNull(serviceName, \"serviceName\");\n    this.httpClient = Preconditions.checkNotNull(httpClient, \"httpClient\");\n    this.serviceLocator = Preconditions.checkNotNull(serviceLocator, \"serviceLocator\");\n    this.retryPolicy = Preconditions.checkNotNull(retryPolicy, \"retryPolicy\");\n    this.connectExec = Preconditions.checkNotNull(connectExec, \"connectExec\");\n\n    if (retryPolicy.maxAttempts() == 0) {\n      throw new IAE(\"Invalid maxAttempts[%d] in retry policy\", retryPolicy.maxAttempts());\n    }\n  }\n\n  @VisibleForTesting\n  public static long computeBackoffMs(final ServiceRetryPolicy retryPolicy, final long attemptNumber)\n  {\n    return Math.max(\n        retryPolicy.minWaitMillis(),\n        Math.min(retryPolicy.maxWaitMillis(), (long) (Math.pow(2, attemptNumber) * retryPolicy.minWaitMillis()))\n    );\n  }\n\n  @Override\n  public <IntermediateType, FinalType> ListenableFuture<FinalType> asyncRequest(\n      final RequestBuilder requestBuilder,\n      final HttpResponseHandler<IntermediateType, FinalType> handler\n  )\n  {","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/server/src/main/java/org/apache/druid/rpc/ServiceClientImpl.java#L63-L99","documentation":"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.","triggerScenarios":"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).","commonSituations":"Config parsing yielding 0 retries and being passed directly as maxAttempts; confusion between 'retry count' and 'attempt count' semantics when constructing the policy.","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."],"exampleFix":"// before\nServiceRetryPolicy policy = new ServiceRetryPolicy(0, backoffFactory);\n// after\nServiceRetryPolicy policy = new ServiceRetryPolicy(3, backoffFactory); // or retries + 1","handlingStrategy":"validation","validationCode":"if (retryPolicy.maxAttempts() < 1) {\n  retryPolicy = new ServiceRetryPolicy(1, retryPolicy.backoffFactory()); // or retries + 1\n}","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["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"],"tags":["rpc","retry","validation","configuration"],"backgroundTag":"invalid-config-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}