Blankj/AndroidUtilCode · error · IllegalArgumentException

The duration is less than 0.

Error message

The duration is less than 0.

What it means

DebouncingUtils.isValid(String, long) records curTime + duration as the 'valid-until' timestamp for a key. A negative duration would set a valid-until time in the past, instantly blocking every subsequent call, so the method rejects negative durations with IllegalArgumentException. Zero is allowed and means 'no debounce'.

Source

Thrown at lib/utilcode/src/main/java/com/blankj/utilcode/util/DebouncingUtils.java:64

     * @return {@code true}: yes<br>{@code false}: no
     */
    public static boolean isValid(@NonNull final View view, final long duration) {
        return isValid(String.valueOf(view.hashCode()), duration);
    }

    /**
     * Return whether the key is not in a jitter state.
     *
     * @param key      The key.
     * @param duration The duration.
     * @return {@code true}: yes<br>{@code false}: no
     */
    public static boolean isValid(@NonNull String key, final long duration) {
        if (TextUtils.isEmpty(key)) {
            throw new IllegalArgumentException("The key is null.");
        }
        if (duration < 0) {
            throw new IllegalArgumentException("The duration is less than 0.");
        }
        long curTime = SystemClock.elapsedRealtime();
        clearIfNecessary(curTime);
        Long validTime = KEY_MILLIS_MAP.get(key);
        if (validTime == null || curTime >= validTime) {
            KEY_MILLIS_MAP.put(key, curTime + duration);
            return true;
        }
        return false;
    }

    private static void clearIfNecessary(long curTime) {
        if (KEY_MILLIS_MAP.size() < CACHE_SIZE) return;
        for (Iterator<Map.Entry<String, Long>> it = KEY_MILLIS_MAP.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<String, Long> entry = it.next();
            Long validTime = entry.getValue();
            if (curTime >= validTime) {
                it.remove();

View on GitHub (pinned to 7b4caf9e54)

Solutions

  1. Coerce the duration: use Math.max(0, duration) so negative/unset values become 'no debounce' instead of throwing.
  2. Translate a -1 'disabled' sentinel into 0 (or skip the call entirely) at the boundary where you read the config.
  3. Validate the duration at its source and reject/flag negative values there rather than inside isValid.
  4. If the duration is computed from timestamps, clamp the subtraction result to >= 0.

Example fix

// before
boolean ok = DebouncingUtils.isValid(key, debounceMs); // debounceMs == -1

// after
long duration = debounceMs < 0 ? 0 : debounceMs;
boolean ok = DebouncingUtils.isValid(key, duration);
Defensive patterns

Strategy: validation

Validate before calling

// Validate duration before debouncing
long duration = debounceMs;
if (duration < 0) {
    duration = 0; // or skip the call
}
boolean ok = DebouncingUtils.isValid(key, duration);

Type guard

// Coerce duration to a valid value
public static long safeDebounce(long d) {
    return Math.max(0, d);
}

Try / catch

try {
    boolean ok = DebouncingUtils.isValid(key, duration);
} catch (IllegalArgumentException e) {
    // duration was negative; clamp and retry
    ok = DebouncingUtils.isValid(key, Math.max(0, duration));
}

Prevention

When it happens

Trigger: Calling isValid(key, duration) where duration is negative — typically an uninitialized long field defaulting through some path, an arithmetic underflow (elapsed - now when now > elapsed), or a config value of -1 used as a sentinel.

Common situations: Reading a debounce window from config with a -1 'disabled' sentinel and passing it straight through; computing duration as a deadline minus current time that goes negative when the deadline already passed; copy-pasting a timeout constant that is signed elsewhere.

Related errors


AI-assisted analysis of Blankj/AndroidUtilCode@7b4caf9e54 (2026-08-14). Data as JSON: /api/errors/921d8b1155b22233. Report an issue: GitHub.