nestjs/nest · error
An unsupported message was received. It has been negative ac
Error message
An unsupported message was received. It has been negative acknowledged, so it will not be re-delivered. Pattern: ${pattern} What it means
ServerRMQ received an incoming request (a packet with an `id`, i.e. one expecting a reply) whose serialized pattern matches no registered @MessageHandler (getHandlerByPattern returned undefined). In manual-ack mode (noAck false, the default) the server negative-acknowledges the message with requeue=false — RabbitMQ drops it permanently — and logs this warning; the requesting client instead receives an error response carrying the constant NO_MESSAGE_HANDLER ('There is no matching message handler defined in the remote service.'). With noAck: true the nack and warning are skipped but the drop is equally silent.
Source
Thrown at packages/microservices/server/server-rmq.ts:313
if (isNil(message)) {
return;
}
const { content, properties } = message;
const rawMessage = this.parseMessageContent(content);
const packet = await this.deserializer.deserialize(rawMessage, properties);
const pattern = isString(packet.pattern)
? packet.pattern
: JSON.stringify(packet.pattern);
const rmqContext = new RmqContext([message, channel, pattern]);
if (isUndefined((packet as IncomingRequest).id)) {
return this.handleEvent(pattern, packet, rmqContext);
}
const handler = this.getHandlerByPattern(pattern);
if (!handler) {
if (!this.noAck) {
this.logger.warn(RMQ_NO_MESSAGE_HANDLER`${pattern}`);
this.channel!.nack(rmqContext.getMessage() as Message, false, false);
}
const status = 'error';
const noHandlerPacket = {
id: (packet as IncomingRequest).id,
err: NO_MESSAGE_HANDLER,
status,
};
return this.sendMessage(
noHandlerPacket,
properties.replyTo,
properties.correlationId,
rmqContext,
);
}
return this.onProcessingStartHook(
this.transportId,
rmqContext,View on GitHub (pinned to dd75d7bd8c)
Solutions
- Make the pattern identical on both sides — exact string, or a shared object constant — and remember object patterns are matched by their JSON serialization, so key order matters.
- Give each microservice its own queue; a shared queue round-robins messages to instances that may not have the handler.
- Confirm the module containing the handlers is actually imported before producers go live (inspect registered patterns at startup).
- Configure a dead-letter exchange on the queue so nacked unsupported messages are captured for inspection instead of silently dropped, and purge stale messages after renaming/removing patterns.
- During rolling deploys, ship the consumer that handles both old and new patterns before switching producers.
Example fix
// before
// producer
this.client.send({ cmd: 'get-user' }, id);
// consumer
@MessageHandler({ cmd: 'get_user' }) // mismatch -> nack, message dropped
// after: one shared constant (shared package)
export const GET_USER = { cmd: 'get-user' } as const;
// producer
this.client.send(GET_USER, id);
// consumer
@MessageHandler(GET_USER)
getUser(@Payload() id: string) { /* ... */ } Defensive patterns
Strategy: validation
Validate before calling
// share one source of truth for patterns and validate before publishing
export const PATTERNS = ['users.get', 'users.create'] as const;
export type Pattern = (typeof PATTERNS)[number];
function assertKnownPattern(p: string): asserts p is Pattern {
if (!(PATTERNS as readonly string[]).includes(p)) {
throw new Error(`Unknown pattern '${p}': server has no @MessageHandler for it`);
}
}
assertKnownPattern(pattern);
this.client.send(pattern, payload); Type guard
const PATTERNS = ['users.get', 'users.create'] as const; type Pattern = (typeof PATTERNS)[number]; const isPattern = (p: string): p is Pattern => (PATTERNS as readonly string[]).includes(p);
Try / catch
this.client.send<string, User>(pattern, payload).subscribe({
next: (user) => this.logger.log(user),
error: (err) => {
// server replied with the NO_MESSAGE_HANDLER constant
if (/no matching message handler/i.test(String(err?.message ?? err))) {
// pattern contract is broken: alert and stop retrying, this is not transient
}
},
}); Prevention
- Export pattern constants from a shared package instead of retyping strings per service
- Use a dedicated queue per microservice; never share queues between services with different handlers
- Configure a dead-letter exchange so nacked unsupported messages are captured, and monitor it
- Run contract/e2e tests covering every published pattern against the consumer before deploy
- Deploy the consumer before the producer when introducing new patterns; purge queues when renaming patterns
When it happens
Trigger: client.send(pattern, payload) where the pattern does not exactly equal a @MessageHandler(pattern) on the server: typos, casing differences, or object patterns whose JSON.stringify key order differs (matching uses the serialized string). Also raw JSON published to the service's queue by external producers lacking the {id, pattern, data} contract, and version skew where a new producer ships before the consumer that handles the new pattern.
Common situations: Pattern typos between services; renaming a handler while old messages sit in the queue; multiple microservices configured with the same queue so messages round-robin to an instance that never registered the pattern; rolling deploys with old and new pattern names; third-party systems publishing directly to the queue.
Related errors
- An unsupported event was received. It has been negative ackn
- RMQ broker has blocked the connection (flow control). Reason
- RMQ broker has blocked the connection (flow control). Reason
AI-assisted analysis of nestjs/nest@dd75d7bd8c (2026-08-21).
Data as JSON: /api/errors/b466079baa4b1e90.
Report an issue: GitHub.