{"record":{"id":"a6703353185fcd71","repo":"alibaba/spring-ai-alibaba","slug":"maxretries-must-be-0","errorCode":null,"errorMessage":"maxRetries must be >= 0","messagePattern":"maxRetries must be >= 0","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/toolretry/ToolRetryInterceptor.java","lineNumber":195,"sourceCode":"\t\t * @param maxAttempts total attempts (initial call plus retries)\n\t\t */\n\t\tpublic Builder maxAttempts(int maxAttempts) {\n\t\t\tif (maxAttempts < 1) {\n\t\t\t\tthrow new IllegalArgumentException(\"maxAttempts must be >= 1\");\n\t\t\t}\n\t\t\tthis.maxAttempts = maxAttempts;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * Set the maximum number of retries (excluding the first call).\n\t\t * @param maxRetries number of retries; total attempts will be {@code maxRetries + 1}\n\t\t * @deprecated use {@link #maxAttempts(int)} instead, which counts the initial call\n\t\t */\n\t\t@Deprecated\n\t\tpublic Builder maxRetries(int maxRetries) {\n\t\t\tif (maxRetries < 0) {\n\t\t\t\tthrow new IllegalArgumentException(\"maxRetries must be >= 0\");\n\t\t\t}\n\t\t\tthis.maxAttempts = maxRetries + 1;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder toolNames(Set<String> toolNames) {\n\t\t\tthis.toolNames = toolNames;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder toolName(String toolName) {\n\t\t\tif (this.toolNames == null) {\n\t\t\t\tthis.toolNames = new HashSet<>();\n\t\t\t}\n\t\t\tthis.toolNames.add(toolName);\n\t\t\treturn this;\n\t\t}\n","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/toolretry/ToolRetryInterceptor.java#L177-L213","documentation":"ToolRetryInterceptor's deprecated Builder.maxRetries(int) validates that the retry count is non-negative, throwing IllegalArgumentException when a negative value is passed. maxRetries counts extra retries, so total attempts = maxRetries + 1. The library rejects negative numbers because a negative retry count is meaningless.","triggerScenarios":"Calling ToolRetryInterceptor.Builder.maxRetries(-1) (or any negative int) instead of using the replacement maxAttempts(int).","commonSituations":"Computing the retry count from config or environment variables that may be negative or uninitialized; migrating code and passing -1 as a sentinel for 'no retries' instead of 0.","solutions":["Pass 0 to disable retries instead of a negative number","Prefer the non-deprecated maxAttempts(int) builder method, which counts the initial call (total attempts = maxAttempts)","Validate the configured value before building the interceptor"],"exampleFix":"// before\nToolRetryInterceptor.builder().maxRetries(-1).build();\n// after\nToolRetryInterceptor.builder().maxAttempts(1).build(); // 1 total attempt, no retries","handlingStrategy":"validation","validationCode":"if (retries < 0) throw new IllegalArgumentException(\"Configure retries >= 0, or use maxAttempts >= 1\");","typeGuard":"boolean isValidRetryCount(int n) { return n >= 0; }","tryCatchPattern":"try { b.maxRetries(n); } catch (IllegalArgumentException e) { b.maxAttempts(Math.max(1, n + 1)); }","preventionTips":["Use the non-deprecated maxAttempts(int) API, which counts the initial call","Treat 0 as the 'disable retries' value, never -1","Validate retry values when loading from config/env"],"tags":["java","validation","builder","retry"],"backgroundTag":"invalid-argument-value","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}