apache/rocketmq · error · MQClientException

offset < 0

Error message

offset < 0

What it means

Thrown by pullSyncImpl when the requested starting offset is negative. Pull offsets are logical positions into a commit log queue; a negative offset is meaningless and is rejected before any network call is made.

Source

Thrown at client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPullConsumerImpl.java:239

        try {
            return FilterAPI.build(mq.getTopic(),
                messageSelector.getExpression(), messageSelector.getExpressionType());
        } catch (Exception e) {
            throw new MQClientException("parse subscription error", e);
        }
    }

    private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, boolean block,
        long timeout)
        throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        this.isRunning();

        if (null == mq) {
            throw new MQClientException("mq is null", null);
        }

        if (offset < 0) {
            throw new MQClientException("offset < 0", null);
        }

        if (maxNums <= 0) {
            throw new MQClientException("maxNums <= 0", null);
        }

        this.subscriptionAutomatically(mq.getTopic());

        int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);

        long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;

        boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
        PullResult pullResult = this.pullAPIWrapper.pullKernelImpl(
            mq,
            subscriptionData.getSubString(),
            subscriptionData.getExpressionType(),
            isTagType ? 0L : subscriptionData.getSubVersion(),

View on GitHub (pinned to 293f588571)

Solutions

  1. Use consumer.fetchMessageQueuesWithQueueOffset(...) or the OffsetStore to obtain a valid starting offset
  2. Clamp computed offsets: long safe = Math.max(0, offset)
  3. For 'start at earliest/latest' semantics query minOffset/maxOffset from the broker instead of inventing sentinels

Example fix

// before
long offset = currentOffset - backTrack; // may go negative
consumer.pull(mq, "*", offset, 32);

// after
long offset = Math.max(mqMinOffset, currentOffset - backTrack);
consumer.pull(mq, "*", offset, 32);
Defensive patterns

Strategy: validation

Validate before calling

if (offset < 0) throw new IllegalArgumentException("offset must be >= 0, got " + offset);

Prevention

When it happens

Trigger: DefaultMQPullConsumer.pull(mq, expr, offset, maxNums) with offset < 0; computing offset as 'minOffset - diff' or 'offset - pulled' that underflows; passing -1 as a sentinel 'start from beginning' value instead of fetchMessageQueuesWithQueueOffset.

Common situations: Developers using -1 to mean 'latest' (the correct sentinel is obtained from fetchMessageQueueOffset / maxOffset); arithmetic that subtracts more than the current offset; offsets loaded from a corrupt external store.

Related errors


AI-assisted analysis of apache/rocketmq@293f588571 (2026-08-14). Data as JSON: /api/errors/0e525822eadc3532. Report an issue: GitHub.