apache/rocketmq · error · IllegalStateException

setAssignTag only can be called before start.

Error message

setAssignTag only can be called before start.

What it means

setSubExpressionForAssign throws IllegalStateException("setAssignTag only can be called before start.") when the consumer's serviceState is not CREATE_JUST — i.e. start() has already been called (RUNNING or SHUTDOWN_ALREADY). The tag expression must be baked into the subscription before the client factory builds subscriptions and starts pull tasks, hence it cannot change afterwards.

Source

Thrown at client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java:592

    }

    public synchronized void assign(Collection<MessageQueue> messageQueues) {
        if (messageQueues == null || messageQueues.isEmpty()) {
            throw new IllegalArgumentException("Message queues can not be null or empty.");
        }
        setSubscriptionType(SubscriptionType.ASSIGN);
        assignedMessageQueue.updateAssignedMessageQueue(messageQueues);
        if (serviceState == ServiceState.RUNNING) {
            updateAssignPullTask(messageQueues);
        }
    }

    public synchronized void setSubExpressionForAssign(final String topic, final String subExpression) {
        if (StringUtils.isBlank(subExpression)) {
            throw new IllegalArgumentException("subExpression can not be null or empty.");
        }
        if (serviceState != ServiceState.CREATE_JUST) {
            throw new IllegalStateException("setAssignTag only can be called before start.");
        }
        setSubscriptionType(SubscriptionType.ASSIGN);
        topicToSubExpression.put(topic, subExpression);
    }

    private void maybeAutoCommit() {
        long now = System.currentTimeMillis();
        if (now >= nextAutoCommitDeadline) {
            commitAll();
            nextAutoCommitDeadline = now + defaultLitePullConsumer.getAutoCommitIntervalMillis();
        }
    }

    public synchronized List<MessageExt> poll(long timeout) {
        try {
            checkServiceState();
            if (timeout < 0) {
                throw new IllegalArgumentException("Timeout must not be negative");

View on GitHub (pinned to 293f588571)

Solutions

  1. Move the setSubExpressionForAssign call before consumer.start() in your lifecycle code
  2. To change the filter at runtime, unsubscribe/assign again on a fresh consumer instance instead
  3. Fix bean initialization order so configuration precedes start

Example fix

// before
consumer.assign(queues);
consumer.start();
consumer.setSubExpressionForAssign(topic, "TAG_A");

// after
consumer.assign(queues);
consumer.setSubExpressionForAssign(topic, "TAG_A");
consumer.start();
Defensive patterns

Strategy: validation

Try / catch

try {
    consumer.setSubExpressionForAssign(topic, expr);
} catch (IllegalStateException e) {
    // started already: recreate the consumer or drop the reconfiguration
}

Prevention

When it happens

Trigger: Calling setSubExpressionForAssign(topic, expr) after consumer.start(); calling it on a consumer that was started and then shut down.

Common situations: Reconfiguring a long-lived consumer bean at runtime; initialization-order bug where @PostConstruct start() runs before the expression setter.

Related errors


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