ReactiveX/RxJava · error · IllegalArgumentException
count >= 0 expected but it was {}
Error message
count >= 0 expected but it was {} What it means
Thrown by Streamable.skip(long count) when count is negative. skip() drops the first `count` items and relays the rest; a negative skip is undefined and rejected at assembly time. Note this message uses the word 'expected' whereas repeat/take use 'required' — a minor wording inconsistency in the library, but the contract is identical.
Source
Thrown at src/main/java/io/reactivex/rxjava4/core/Streamable.java:1020
*/
@CheckReturnValue
@NonNull
default Streamable<T> retryWhen(BiFunction<? super Long, ? super Throwable, ? extends CompletionStage<Boolean>> whenFunction) {
Objects.requireNonNull(whenFunction, "whenFunction is null");
return RxJavaPlugins.onAssembly(new StreamableRetry<>(this, whenFunction));
}
/**
* Skips the first {@code count} items and relays the rest to the downstream.
* @param count the number of items to skip
* @return the new {@code Streamable} instance
* @throws IllegalArgumentException if {@code count} is negative
*/
@CheckReturnValue
@NonNull
default Streamable<T> skip(long count) {
if (count < 0) {
throw new IllegalArgumentException("count >= 0 expected but it was " + count);
}
return RxJavaPlugins.onAssembly(new StreamableSkip<>(this, count));
}
/**
* Takes at most the given number of items from the upstream and relays it to the downstream,
* then cancels the rest of the sequence.
* <p>
* Note that cancellation of the upstream happens when the downstream
* calls {@link Streamer#next()} because unlike the push-based {@code take}
* implementations, the current upstream value has to remain accessible until
* the downstream calls {@code next} or {@link Streamer#finish()}.
* @param count the maximum number of items to relay
* @return the new {@code Streamable} instance
* @throws IllegalArgumentException if {@code count} is negative
*/
@CheckReturnValue
@NonNullView on GitHub (pinned to a8ab535614)
Solutions
- Clamp the skip value to >= 0 before calling skip(), treating negatives as 0 (skip nothing).
- Validate page/offset inputs at the API/controller boundary so negative values never reach the stream pipeline.
- Use Math.max(0, n) at the call site if a small number of calls are involved; otherwise centralize in a helper.
- Test the boundary: skip(0) relays everything, skip(large) relays nothing, skip(-1) throws.
Example fix
// before long toSkip = pageSize * (page - 1); // page=0 -> negative source.skip(toSkip).blockingSubscribe(...); // after long toSkip = Math.max(0, pageSize * (page - 1)); source.skip(toSkip).blockingSubscribe(...);
Defensive patterns
Strategy: validation
Validate before calling
long safeSkip(long count) {
return Math.max(0, count);
}
// then: source.skip(safeSkip(n)) Try / catch
try {
source.skip(n).blockingSubscribe(...);
} catch (IllegalArgumentException e) {
logger.warn("Invalid skip count {}", n, e);
} Prevention
- Validate page/offset inputs at the API boundary so negatives never reach skip().
- When converting 1-based page numbers to skip amounts, guard page >= 1 first.
- Clamp skip with Math.max(0, expr) at every call site that uses computed offsets.
When it happens
Trigger: Calling streamable.skip(n) with n < 0. Typical when skip count is computed from an offset, page-size, or index expression that can underflow (e.g. skip = pageSize * (page - 1) when page is 0 or negative).
Common situations: Pagination math where a zero or negative page number is passed; user-supplied offset not sanitized; converting 1-based indices to 0-based skip values incorrectly.
Related errors
- errors is empty
- Array of size 2 expected but got {}
- Array of size 3 expected but got {}
- Array of size 4 expected but got {}
- Array of size 5 expected but got {}
AI-assisted analysis of ReactiveX/RxJava@a8ab535614 (2026-08-13).
Data as JSON: /api/errors/ef979ff40ad34dbd.
Report an issue: GitHub.