alibaba/spring-cloud-alibaba · warning · IllegalStateException
Already acknowledged
Error message
Already acknowledged
What it means
RocketMQAckCallback.acknowledge throws IllegalStateException('Already acknowledged') if called more than once for the same message. After the first acknowledge the internal `acknowledged` flag is set true in the finally block; a second call is a programming error in the consuming flow.
Source
Thrown at spring-cloud-alibaba-starters/spring-cloud-starter-stream-rocketmq/src/main/java/com/alibaba/cloud/stream/binder/rocketmq/integration/inbound/pull/RocketMQAckCallback.java:76
public boolean isAcknowledged() {
return this.acknowledged;
}
@Override
public void noAutoAck() {
this.autoAckEnabled = false;
}
@Override
public boolean isAutoAck() {
return this.autoAckEnabled;
}
@Override
public void acknowledge(Status status) {
Assert.notNull(status, "'status' cannot be null");
if (this.acknowledged) {
throw new IllegalStateException("Already acknowledged");
}
synchronized (messageQueue) {
try {
long offset = messageExt.getQueueOffset();
switch (status) {
case REJECT, ACCEPT -> consumer.commit(Collections.singleton(messageQueue), false);
case REQUEUE -> consumer.seek(messageQueue, offset);
}
}
catch (MQClientException e) {
throw new IllegalStateException(e);
}
finally {
this.acknowledged = true;
}
}
}
View on GitHub (pinned to 115d590110)
Solutions
- Call acknowledge() exactly once per message; track ack ownership in one place.
- If using auto-ack, do not also manually ack.
- Guard re-entrancy in error/retry paths so the same callback is not acked again.
Example fix
// before ackCallback.acknowledge(Status.ACCEPT); // ... ackCallback.acknowledge(Status.ACCEPT); // second call -> [127] // after ackCallback.acknowledge(Status.ACCEPT); // honor isAcknowledged() before any further ack
Defensive patterns
Strategy: type-guard
Type guard
// Type guard: only ack if not yet acknowledged.
if (!ackCallback.isAcknowledged()) {
ackCallback.acknowledge(Status.ACCEPT);
} else {
log.warn("Skipping duplicate ack for message offset {}", offset);
} Prevention
- Acknowledge each message exactly once.
- Avoid mixing manual and auto ack on the same consumer.
- Centralize ack logic to prevent error/success double-ack.
When it happens
Trigger: Application code (or a downstream handler) invokes AcknowledgmentCallback.acknowledge(...) twice on the same RocketMQAckCallback instance, or the same message is routed through two acking handlers.
Common situations: Manual ack in a try/finally plus framework auto-ack; replay logic re-acking; both error-path and success-path acking the same message; duplicate handler subscriptions.
Related errors
- pull consumer already running. {this.toString()}
- group must be configured for DLQ{topic}
- The message queue is not in assigned list
- group must be configured for DLQ{destination.getName()}
- DefaultMQPushConsumer init failed, Caused by {e.getMessage()
AI-assisted analysis of alibaba/spring-cloud-alibaba@115d590110 (2026-08-14).
Data as JSON: /api/errors/1623e39e8f3d8975.
Report an issue: GitHub.