flowable/flowable-engine · error · FlowableException

max wait timeout has to be positive. It was

Error message

max wait timeout has to be positive. It was 

What it means

Thrown in the WaitForAnyFutureToFinishOperation constructor when a non-null timeout Duration is zero or negative. The agenda requires a strictly positive max wait duration when one is provided; a null timeout is allowed and means wait indefinitely.

Source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/agenda/WaitForAnyFutureToFinishOperation.java:40

import org.flowable.common.engine.api.FlowableException;

/**
 * @author Filip Hrisafov
 */
public class WaitForAnyFutureToFinishOperation implements Runnable {

    protected final Agenda agenda;
    protected final List<ExecuteFutureActionOperation<?>> futureOperations;
    protected final Duration timeout;

    public WaitForAnyFutureToFinishOperation(Agenda agenda, List<ExecuteFutureActionOperation<?>> futureOperations, Duration timeout) {
        this.agenda = agenda;
        this.futureOperations = futureOperations;
        if (timeout == null) {
            this.timeout = null;
        } else if (timeout.isNegative() || timeout.isZero()) {
            throw new FlowableException("max wait timeout has to be positive. It was " + timeout);
        } else {
            this.timeout = timeout;
        }
    }

    @Override
    public void run() {
        CompletableFuture[] anyOfFutures = new CompletableFuture[futureOperations.size()];

        for (int i = 0; i < futureOperations.size(); i++) {
            anyOfFutures[i] = futureOperations.get(i).getFuture();
        }
        try {
            CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(anyOfFutures);
            if (timeout == null) {
                // This blocks until at least one is future is done
                anyOfFuture.get();
            } else {

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Pass Duration.ofMillis(...)/ofSeconds(...) with a strictly positive amount, or pass null to wait without a timeout.
  2. Validate configuration before constructing: if timeout != null && timeout.isZero() || timeout.isNegative() fix the config value.
  3. Fix code computing remaining time so zero/negative remaining durations are treated as null (no timeout) or a minimum positive floor.
  4. Check for unit mistakes when converting config strings (PT0S vs PT1S).

Example fix

// before
Duration timeout = Duration.between(now, deadline); // may be <= 0
new WaitForAnyFutureToFinishOperation(agenda, ops, timeout);
// after
Duration timeout = deadline.isAfter(now) ? Duration.between(now, deadline) : null;
new WaitForAnyFutureToFinishOperation(agenda, ops, timeout);
Defensive patterns

Strategy: validation

Validate before calling

if (timeout != null && (timeout.isZero() || timeout.isNegative())) {
  throw new IllegalArgumentException("max wait timeout must be positive, got " + timeout);
}

Prevention

When it happens

Trigger: Planning a WaitForAnyFutureToFinishOperation with Duration.ZERO, a negative duration (e.g. from a misconfigured property), or a computed duration that underflowed to zero.

Common situations: Configuration value parsed as '0' or 'PT0S'; a computed remaining-time duration that hit zero before the wait was scheduled; unit confusion (e.g. millis passed where seconds expected yielding 0).

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/3e18d83990b0c58d. Report an issue: GitHub.