apache/rocketmq · error · IllegalStateException

Subscribe and assign are mutually exclusive.

Error message

Subscribe and assign are mutually exclusive.

What it means

IllegalStateException thrown by DefaultLitePullConsumerImpl.setSubscriptionType: a DefaultLitePullConsumer supports exactly one consumption mode per lifetime — subscription-based (subscribe()) or manual assignment (assign()). The first call fixes the type (SUBSCRIBE or ASSIGN); a later call of the opposite type throws this.

Source

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

            }
        }
    }

    private void checkServiceState() {
        if (this.serviceState != ServiceState.RUNNING) {
            throw new IllegalStateException(NOT_RUNNING_EXCEPTION_MESSAGE);
        }
    }

    public void updateNameServerAddr(String newAddresses) {
        this.mQClientFactory.getMQClientAPIImpl().updateNameServerAddressList(newAddresses);
    }

    private synchronized void setSubscriptionType(SubscriptionType type) {
        if (this.subscriptionType == SubscriptionType.NONE) {
            this.subscriptionType = type;
        } else if (this.subscriptionType != type) {
            throw new IllegalStateException(SUBSCRIPTION_CONFLICT_EXCEPTION_MESSAGE);
        }
    }

    private void updateAssignedMessageQueue(String topic, Set<MessageQueue> assignedMessageQueue) {
        this.assignedMessageQueue.updateAssignedMessageQueue(topic, assignedMessageQueue);
    }

    private void updatePullTask(String topic, Set<MessageQueue> mqNewSet) {
        Iterator<Map.Entry<MessageQueue, PullTaskImpl>> it = this.taskTable.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<MessageQueue, PullTaskImpl> next = it.next();
            if (next.getKey().getTopic().equals(topic)) {
                if (!mqNewSet.contains(next.getKey())) {
                    next.getValue().setCancelled(true);
                    it.remove();
                }
            }
        }

View on GitHub (pinned to 293f588571)

Solutions

  1. Pick one mode per consumer instance: subscribe() for rebalanced group consumption, assign() for manual queue control
  2. Create a separate DefaultLitePullConsumer instance if you need both patterns
  3. Remove leftover subscribe/assign calls left by refactoring
  4. Restart with a fresh instance if the conflict comes from earlier experiments in the same process

Example fix

// before (one instance, both modes)
consumer.subscribe("T", "*");
consumer.assign(Arrays.asList(mq0, mq1)); // IllegalStateException

// after (dedicated instances)
DefaultLitePullConsumer sub = ...; sub.subscribe("T", "*");
DefaultLitePullConsumer manual = ...; manual.assign(Arrays.asList(mq0, mq1));
Defensive patterns

Strategy: validation

Validate before calling

// enforce one mode per instance in your wrapper
private final Set<Mode> used = EnumSet.noneOf(Mode.class);
public void subscribeOrAssign(Mode m, Runnable r) {
    if (!used.isEmpty() && !used.contains(m)) {
        throw new IllegalStateException("Consumer already in " + used + " mode");
    }
    used.add(m); r.run();
}

Try / catch

try {
    consumer.assign(queues);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("mutually exclusive")) {
        // switch to a fresh consumer instance for assigned mode
    } else throw e;
}

Prevention

When it happens

Trigger: Calling consumer.assign(...) after consumer.subscribe(...) (or vice versa) on the same instance. Each assign()/subscribe() call routes through setSubscriptionType, so mixing them in any order on one consumer triggers the conflict.

Common situations: Copy-pasting samples that use both APIs; refactoring from subscribe to assign without a new instance; utility code that 'tops up' subscriptions with assigns; reusing a consumer bean for different consumption patterns via configuration switches.

Related errors


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