elastic/elasticsearch · error · IllegalArgumentException
prefetchTime [${prefetchTime}] must be greater than or equal
Error message
prefetchTime [${prefetchTime}] must be greater than or equal to staleTime [${staleTime}] What it means
A relational guard: prefetchTime must be >= staleTime. The comment in source explains that if prefetch starts later than stale, the prefetchAt instant would fall after staleAt and the background-refresh window in resolveIdentity() would be unreachable - so the provider would always hit the synchronous fallback. The check fires only after both individual positivity checks pass.
Source
Thrown at libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java:103
private AsyncWebIdentityCredentialsProvider(Builder builder) {
this.roleArn = Objects.requireNonNull(builder.roleArn, "roleArn must not be null");
this.roleSessionName = Objects.requireNonNull(builder.roleSessionName, "roleSessionName must not be null");
this.tokenSupplier = Objects.requireNonNull(builder.tokenSupplier, "tokenSupplier must not be null");
this.stsAsyncClient = Objects.requireNonNull(builder.stsAsyncClient, "stsAsyncClient must not be null");
this.prefetchTime = builder.prefetchTime != null ? builder.prefetchTime : DEFAULT_PREFETCH_TIME;
this.staleTime = builder.staleTime != null ? builder.staleTime : DEFAULT_STALE_TIME;
this.clock = builder.clock != null ? builder.clock : Clock.systemUTC();
if (staleTime.isNegative() || staleTime.isZero()) {
throw new IllegalArgumentException("staleTime must be a positive duration but was [" + staleTime + "]");
}
if (prefetchTime.isNegative() || prefetchTime.isZero()) {
throw new IllegalArgumentException("prefetchTime must be a positive duration but was [" + prefetchTime + "]");
}
// prefetchTime must start no later than staleTime, otherwise prefetchAt would fall after staleAt and the
// background-refresh window in resolveIdentity() would be unreachable.
if (prefetchTime.compareTo(staleTime) < 0) {
throw new IllegalArgumentException(
"prefetchTime [" + prefetchTime + "] must be greater than or equal to staleTime [" + staleTime + "]"
);
}
}
public static Builder builder() {
return new Builder();
}
@Override
public CompletableFuture<AwsCredentialsIdentity> resolveIdentity(ResolveIdentityRequest request) {
Cached current = cache.get();
Instant now = clock.instant();
if (current == null || now.isAfter(current.staleAt())) {
// Nothing usable cached: wait on a refresh, but asynchronously, so the caller's thread is not parked.
return refresh().thenApply(Cached::credentials);
}
if (now.isAfter(current.prefetchAt())) {View on GitHub (pinned to db6a809a66)
Solutions
- Set prefetchTime >= staleTime (e.g. prefetch = stale, or prefetch larger)
- If unsure, leave both null and accept the documented defaults, which already satisfy the invariant
- Re-read the doc comment: prefetchTime is when background refresh kicks in, staleTime is when the cached value is considered unusable - prefetch must not be later than stale
Example fix
// before .prefetchTime(Duration.ofSeconds(10)).staleTime(Duration.ofSeconds(60)).build() // after .prefetchTime(Duration.ofSeconds(60)).staleTime(Duration.ofSeconds(60)).build()
Defensive patterns
Strategy: validation
Validate before calling
if (prefetchTime != null && staleTime != null
&& prefetchTime.compareTo(staleTime) < 0) {
throw new IllegalArgumentException("prefetchTime must be >= staleTime");
}
builder.prefetchTime(prefetchTime).staleTime(staleTime); Type guard
static boolean prefetchNotBeforeStale(Duration prefetch, Duration stale) {
return prefetch == null || stale == null || prefetch.compareTo(stale) >= 0;
} Try / catch
try { builder.prefetchTime(p).staleTime(s).build(); }
catch (IllegalArgumentException e) { /* align the two values */ } Prevention
- Validate the prefetch>=stale invariant in one place
- Leave both null to use vetted defaults
- Document the relationship in the config schema
When it happens
Trigger: Building the provider with prefetchTime strictly less than staleTime (both positive). E.g. prefetchTime = 10s, staleTime = 60s.
Common situations: Treating prefetchTime as "lead time before expiry" and staleTime as "absolute window" and inverting them; copying values from a doc that defined the terms differently; auto-derived values where prefetch shrinks below stale.
Related errors
- staleTime must be a positive duration but was [${staleTime}]
- prefetchTime must be a positive duration but was [${prefetch
- starting java failed with [%d] output: %s error: %s
- CONFIG
- could not resolve ssl client verification mode, unknown valu
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/28557ae9be7b11d4.
Report an issue: GitHub.