LMAX-Exchange/disruptor · error · IllegalArgumentException

Both batchStartsAt and batchSize must be positive but got: b

Error message

Both batchStartsAt and batchSize must be positive but got: batchStartsAt {} and batchSize {}

What it means

Thrown by RingBuffer.checkBatchSizing during batch-publish overloads (publishEvents, tryPublishEvents) when batchStartsAt is negative or batchSize is negative. batchStartsAt is the offset into your argument arrays where the batch begins; both it and the count must be non-negative for the copy into the ring to be well-defined.

Source

Thrown at src/main/java/com/lmax/disruptor/RingBuffer.java:904

     * @return The number of slots remaining.
     */
    @Override
    public long remainingCapacity()
    {
        return sequencer.remainingCapacity();
    }

    private void checkBounds(final EventTranslator<E>[] translators, final int batchStartsAt, final int batchSize)
    {
        checkBatchSizing(batchStartsAt, batchSize);
        batchOverRuns(translators, batchStartsAt, batchSize);
    }

    private void checkBatchSizing(final int batchStartsAt, final int batchSize)
    {
        if (batchStartsAt < 0 || batchSize < 0)
        {
            throw new IllegalArgumentException("Both batchStartsAt and batchSize must be positive but got: batchStartsAt " + batchStartsAt + " and batchSize " + batchSize);
        }
        else if (batchSize > bufferSize)
        {
            throw new IllegalArgumentException("The ring buffer cannot accommodate " + batchSize + " it only has space for " + bufferSize + " entities.");
        }
    }

    private <A> void checkBounds(final A[] arg0, final int batchStartsAt, final int batchSize)
    {
        checkBatchSizing(batchStartsAt, batchSize);
        batchOverRuns(arg0, batchStartsAt, batchSize);
    }

    private <A, B> void checkBounds(final A[] arg0, final B[] arg1, final int batchStartsAt, final int batchSize)
    {
        checkBatchSizing(batchStartsAt, batchSize);
        batchOverRuns(arg0, batchStartsAt, batchSize);
        batchOverRuns(arg1, batchStartsAt, batchSize);

View on GitHub (pinned to c871ca4982)

Solutions

  1. Fix the offset/count math: batchStartsAt must be >= 0 and batchSize >= 1.
  2. To publish from the beginning of the array, pass 0 explicitly rather than -1.
  3. Log batchStartsAt/batchSize before publishing while debugging boundary loops.

Example fix

// before
ringBuffer.publishEvents(translators, offset - translators.length, count); // negative offset

// after
int start = Math.max(0, offset);
ringBuffer.publishEvents(translators, start, count);
Defensive patterns

Strategy: validation

Validate before calling

if (batchStartsAt < 0 || batchSize < 1 || batchStartsAt >= translators.length) {
    throw new IllegalArgumentException("bad batch bounds: start=" + batchStartsAt + " size=" + batchSize);
}

Prevention

When it happens

Trigger: Calling ringBuffer.publishEvents(translators, -1, 2) or publishEvents(translators, 0, -2) — i.e. passing a negative offset or count into any batch publish variant (translators, one-arg, two-arg, three-arg, varargs forms).

Common situations: Offset arithmetic bugs (batchStartsAt = lastEnd - batchLength going negative at a boundary); loop code computing 'start = i - batchSize' incorrectly; passing a sentinel like -1 to mean 'from the start'.

Related errors


AI-assisted analysis of LMAX-Exchange/disruptor@c871ca4982 (2026-08-14). Data as JSON: /api/errors/a4ad639e14e7df18. Report an issue: GitHub.