{"record":{"id":"7e08f26ebfe8c421","repo":"elastic/elasticsearch","slug":"prefetchtime-must-be-a-positive-duration-but-was","errorCode":null,"errorMessage":"prefetchTime must be a positive duration but was [${prefetchTime}]","messagePattern":"prefetchTime must be a positive duration but was \\[(.+?)\\]","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java","lineNumber":98,"sourceCode":"    private final Duration staleTime;\n    private final Clock clock;\n\n    private final AtomicReference<Cached> cache = new AtomicReference<>();\n    private final AtomicReference<CompletableFuture<Cached>> inFlight = new AtomicReference<>();\n\n    private AsyncWebIdentityCredentialsProvider(Builder builder) {\n        this.roleArn = Objects.requireNonNull(builder.roleArn, \"roleArn must not be null\");\n        this.roleSessionName = Objects.requireNonNull(builder.roleSessionName, \"roleSessionName must not be null\");\n        this.tokenSupplier = Objects.requireNonNull(builder.tokenSupplier, \"tokenSupplier must not be null\");\n        this.stsAsyncClient = Objects.requireNonNull(builder.stsAsyncClient, \"stsAsyncClient must not be null\");\n        this.prefetchTime = builder.prefetchTime != null ? builder.prefetchTime : DEFAULT_PREFETCH_TIME;\n        this.staleTime = builder.staleTime != null ? builder.staleTime : DEFAULT_STALE_TIME;\n        this.clock = builder.clock != null ? builder.clock : Clock.systemUTC();\n        if (staleTime.isNegative() || staleTime.isZero()) {\n            throw new IllegalArgumentException(\"staleTime must be a positive duration but was [\" + staleTime + \"]\");\n        }\n        if (prefetchTime.isNegative() || prefetchTime.isZero()) {\n            throw new IllegalArgumentException(\"prefetchTime must be a positive duration but was [\" + prefetchTime + \"]\");\n        }\n        // prefetchTime must start no later than staleTime, otherwise prefetchAt would fall after staleAt and the\n        // background-refresh window in resolveIdentity() would be unreachable.\n        if (prefetchTime.compareTo(staleTime) < 0) {\n            throw new IllegalArgumentException(\n                \"prefetchTime [\" + prefetchTime + \"] must be greater than or equal to staleTime [\" + staleTime + \"]\"\n            );\n        }\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    @Override\n    public CompletableFuture<AwsCredentialsIdentity> resolveIdentity(ResolveIdentityRequest request) {\n        Cached current = cache.get();\n        Instant now = clock.instant();","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java#L80-L116","documentation":"Symmetric to the staleTime check: the builder rejects a prefetchTime Duration that is zero or negative. prefetchTime controls how far before expiry the background refresh is scheduled; non-positive would never schedule a prefetch.","triggerScenarios":"Calling .prefetchTime(Duration.ZERO) or a negative Duration on the provider builder.","commonSituations":"Same family as staleTime: zero-valued config, misunderstanding that prefetch must be a positive lead time, arithmetic underflow when computing from expiry.","solutions":["Set prefetchTime to a positive Duration (e.g. Duration.ofMinutes(1))","Use a small positive value rather than zero for near-expiry refresh","Validate the value when reading from configuration"],"exampleFix":"// before\n.prefetchTime(Duration.ZERO).build()\n// after\n.prefetchTime(Duration.ofSeconds(15)).build()","handlingStrategy":"validation","validationCode":"if (prefetchTime == null || prefetchTime.isZero() || prefetchTime.isNegative()) {\n    throw new IllegalArgumentException(\"prefetchTime must be positive: \" + prefetchTime);\n}\nbuilder.prefetchTime(prefetchTime);","typeGuard":"static boolean isPositiveDuration(Duration d) {\n    return d != null && !d.isZero() && !d.isNegative();\n}","tryCatchPattern":"try { builder.prefetchTime(d).build(); }\ncatch (IllegalArgumentException e) { /* use default */ }","preventionTips":["Never pass Duration.ZERO intending 'refresh now'","Validate both prefetch and stale together","Use a shared positivity helper"],"tags":["aws","workload-identity","credentials","config","startup"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}