14.0.0-canary.1 (2025-08-31)
SQS Consumer v14.0.0 Migration Guide
Overview
Version 14.0.0 introduces a breaking change to how message acknowledgment works. This change makes acknowledgment behavior more explicit and predictable, addressing user confusion about when messages are acknowledged.
What Changed
Previous Behavior (<=v13.x)
- Messages were acknowledged automatically in certain scenarios
- Returning
void
(nothing) from handlers had implicit acknowledgment behavior - Users were often unclear about when messages would be acknowledged
New Behavior (>=v14.0)
- Explicit acknowledgment only: Messages are acknowledged only when explicitly returned
- Returning
void
(nothing) means no acknowledgment by default - Clear, predictable behavior based on what your handler returns
Benefits of the New Approach
- Clarity: Explicit acknowledgment makes code intent clear
- Reliability: Reduce accidental acknowledgment of failed processing
- Debugging: Easier to trace acknowledgment behavior
- Flexibility: Fine-grained control over which messages are acknowledged
Migration Steps
handleMessage()
Step 1: Identify Your Current Handler Pattern
First, determine which pattern your current message handlers follow:
// Pattern A: Handler that processes and implicitly acknowledges
const handleMessage = async (message) => {
await processMessage(message);
// No explicit return - was implicitly acknowledged in v13.x
};
// Pattern B: Handler with conditional acknowledgment
const handleMessage = async (message) => {
const success = await processMessage(message);
if (success) {
return message; // Only acknowledge on success
}
// Don't acknowledge on failure
};
// Pattern C: Error handling
const handleMessage = async (message) => {
try {
await processMessage(message);
} catch (error) {
// Message acknowledgment behavior was unclear
throw error;
}
};
Step 2: Update Your Handlers
For Pattern A (Implicit Acknowledgment)
Option 1: Explicit Return
const consumer = Consumer.create({
queueUrl: 'your-queue-url',
handleMessage: async (message) => {
console.log('Processing:', message.Body);
await processMessage(message);
return message; // Explicit acknowledgment
}
});
Option 2: Use alwaysAcknowledge
const consumer = Consumer.create({
queueUrl: 'your-queue-url',
alwaysAcknowledge: true, // Maintains v13.x behavior
handleMessage: async (message) => {
console.log('Processing:', message.Body);
await processMessage(message);
// Will be acknowledged due to alwaysAcknowledge setting
}
});
For Pattern B and C (Conditional Acknowledgment)
const consumer = Consumer.create({
queueUrl: 'your-queue-url',
handleMessage: async (message) => {
try {
await processMessage(message);
return message; // Acknowledge on success
} catch (error) {
console.error('Processing failed:', error);
// Don't return anything - message won't be acknowledged
// It will be retried based on your queue's redrive policy
}
}
});
handleMessageBatch()
This is largely similar to the above recommendations, you just need to adjust to build up an array of the messages that you want to acknowledge and then return that array.
const handleMessageBatch = async (messages) => {
const processed = [];
for (const message of messages) {
try {
await processMessage(message);
processed.push(message); // Add to acknowledgment list
} catch (error) {
console.error('Failed to process message:', error);
// Don't add to processed array - won't be acknowledged
}
}
return processed; // Only acknowledge successfully processed messages
};
Configuration Options
alwaysAcknowledge Setting
Use this setting to maintain <=v13.x behavior during migration:
const consumer = Consumer.create({
queueUrl: 'your-queue-url',
alwaysAcknowledge: true, // Messages acknowledged regardless of return value
handleMessage: async (message) => {
await processMessage(message);
// Will be acknowledged due to alwaysAcknowledge: true
}
});
⚠️ Warning: alwaysAcknowledge: true
should be used as a temporary migration aid. The explicit return pattern is recommended for long-term maintainability.