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
- Move the setSubExpressionForAssign call before consumer.start() in your lifecycle code
- To change the filter at runtime, unsubscribe/assign again on a fresh consumer instance instead
- 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
- Order lifecycle strictly: configure (assign/set expression) -> start() -> poll()
- For runtime filter changes, build a new consumer instance rather than reconfiguring
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
- The consumer is not in running status, {serviceState}
- The consumer not running, please start it first.
- Subscribe and assign are mutually exclusive.
- The PullConsumer service state not OK, maybe started once,
- The consumer group[
AI-assisted analysis of apache/rocketmq@293f588571 (2026-08-14).
Data as JSON: /api/errors/c1dbaf3a840bb16a.
Report an issue: GitHub.