elastic/elasticsearch · error · IllegalArgumentException

prefetchTime must be a positive duration but was [${prefetch

Error message

prefetchTime must be a positive duration but was [${prefetchTime}]

What it means

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.

Source

Thrown at libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java:98

    private final Duration staleTime;
    private final Clock clock;

    private final AtomicReference<Cached> cache = new AtomicReference<>();
    private final AtomicReference<CompletableFuture<Cached>> inFlight = new AtomicReference<>();

    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();

View on GitHub (pinned to db6a809a66)

Solutions

  1. Set prefetchTime to a positive Duration (e.g. Duration.ofMinutes(1))
  2. Use a small positive value rather than zero for near-expiry refresh
  3. Validate the value when reading from configuration

Example fix

// before
.prefetchTime(Duration.ZERO).build()
// after
.prefetchTime(Duration.ofSeconds(15)).build()
Defensive patterns

Strategy: validation

Validate before calling

if (prefetchTime == null || prefetchTime.isZero() || prefetchTime.isNegative()) {
    throw new IllegalArgumentException("prefetchTime must be positive: " + prefetchTime);
}
builder.prefetchTime(prefetchTime);

Type guard

static boolean isPositiveDuration(Duration d) {
    return d != null && !d.isZero() && !d.isNegative();
}

Try / catch

try { builder.prefetchTime(d).build(); }
catch (IllegalArgumentException e) { /* use default */ }

Prevention

When it happens

Trigger: Calling .prefetchTime(Duration.ZERO) or a negative Duration on the provider builder.

Common situations: Same family as staleTime: zero-valued config, misunderstanding that prefetch must be a positive lead time, arithmetic underflow when computing from expiry.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/7e08f26ebfe8c421. Report an issue: GitHub.