apache/beam · error · IllegalArgumentException

timerFrequency must be greater than zero

Error message

timerFrequency must be greater than zero

What it means

AsyncWrapper schedules a timer-driven maintenance tick using a user-supplied timerFrequency Duration. If the duration's milliseconds are zero or negative the timer loop would spin or never fire correctly, so the constructor throws IllegalArgumentException. This validates configuration at pipeline construction time rather than failing at runtime.

Source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/AsyncWrapper.java:197

        idFn,
        useThreadPool,
        null);
  }

  public AsyncWrapper(
      DoFn<InputT, OutputT> syncFn,
      int parallelism,
      Duration timerFrequency,
      @Nullable Integer maxItemsToBuffer,
      @Nullable Duration timeout,
      @Nullable Duration maxWaitTime,
      @Nullable SerializableFunction<InputT, Object> idFn,
      boolean useThreadPool,
      @Nullable Coder<KV<K, InputT>> coder) {
    this.syncFn = syncFn;
    this.parallelism = parallelism;
    if (timerFrequency.getMillis() <= 0) {
      throw new IllegalArgumentException("timerFrequency must be greater than zero");
    }
    this.timerFrequency = timerFrequency;
    this.maxItemsToBuffer =
        (maxItemsToBuffer != null)
            ? maxItemsToBuffer
            : Math.max(parallelism * 2, DEFAULT_MIN_BUFFER_CAPACITY);
    this.timeout = (timeout != null) ? timeout : Duration.standardSeconds(DEFAULT_TIMEOUT_SEC);
    this.maxWaitTime =
        (maxWaitTime != null) ? maxWaitTime : Duration.millis(DEFAULT_MAX_WAIT_TIME_MS);
    this.idFn =
        (idFn != null)
            ? idFn
            : (SerializableFunction<InputT, Object>)
                input -> java.util.Objects.requireNonNull(input);
    this.useThreadPool = useThreadPool;
    this.uuid = UUID.randomUUID().toString();
    this.toProcessSpec = (coder != null) ? StateSpecs.bag(coder) : StateSpecs.bag();
  }

View on GitHub (pinned to 12126d8942)

Solutions

  1. Pass a positive Duration, e.g. Duration.millis(100) or larger
  2. Guard config-driven values: if (millis <= 0) millis = defaultTimerFrequencyMillis
  3. If you want a pure callback-driven design, check whether the wrapper supports disabling timers instead of using 0

Example fix

// before
AsyncWrapper.wrap(fn, parallelism).withTimerFrequency(Duration.ZERO);
// after
AsyncWrapper.wrap(fn, parallelism).withTimerFrequency(Duration.millis(100));
Defensive patterns

Strategy: validation

Validate before calling

if (timerFrequency == null || timerFrequency.getMillis() <= 0) {
  timerFrequency = Duration.millis(100);
}

Try / catch

try {
  builder.withTimerFrequency(freq);
} catch (IllegalArgumentException e) {
  builder.withTimerFrequency(Duration.millis(100));
}

Prevention

When it happens

Trigger: Building an AsyncWrapper (via its builder/constructor) with Duration.ZERO, Duration.millis(0), or a negative Duration for timerFrequency.

Common situations: Developers pass Duration.ZERO assuming 'as fast as possible', or compute the duration from config where a 0 default slipped through.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/7f0f8d17ff398a7b. Report an issue: GitHub.