[FEEDBACK REQUEST] Should AWS Lambda deprecate callback-style function handlers for Node.js 24 and later?
Opened this issue Β· 60 comments
π π The AWS Lambda Runtimes team would love your thoughts! Please upvote/downvote to indicate if you support this change.
Currently, AWS Lambda supports both callback-style and async/await function handler signatures. Considering Node.js has fully embraced async/await, we are considering removing support for callback style function handlers starting with our Node.js 24 release later this year. With this change, customers currently using the callback signature will need to update their code to use the async/await signature when migrating their functions to Node.js 24 or later runtimes.
This proposal applies to Node.js 24 and later runtimes only. We will continue to support both callback and async/await function handler signatures in our existing Node.js 18, Node.js 20, and Node.js 22 runtimes.
Current callback-style: (to be removed)
exports.handler = (event, context, callback) => {
// ... handler logic
callback(null, { statusCode: 200, body: 'Success!' });
};Async/await style: (to be kept as-is)
export const handler = async (event, context) => {
// ... handler logic
return { statusCode: 200, body: 'Success!' };
};Should we make this change? Please vote!
π : Yes, in favor of deprecating the callback handler signature.
π : No, keep the callback handler signature.
Please also share any feedback in the comments.
Definitely deprecate it.
People either use it accidentally (due to old examples or documentation), or think that setting callbackWaitsForEmptyEventLoop to false is a way to perform compute after the handler exits (not realizing that those promises are only driven during subsequent invocations and can fail silently or dropped entirely on shutdown/timeout/suppressed init).
Yes, sir
absolutely deprecate it please!
I've add this to the Middy roadmap to revisit for future major releases. We dropped support for callbacks many yers ago, but still have references within the code.
Yes
I literally never use it and I agree with the general sentiment of deprecating it, but the proposal is missing some info on what are the benefits of doing this. Will it help customers in any way, or will it bring performance improvements, or?
I imagine that having this info would help others who still use it better evaluate the tradeoffs.
Yes deprecated please
π
+1
I literally never use it and I agree with the general sentiment of deprecating it, but the proposal is missing some info on what are the benefits of doing this. Will it help customers in any way, or will it bring performance improvements, or?
I imagine that having this info would help others who still use it better evaluate the tradeoffs.
Yes please, deprecate the callback pattern.
Although not a major issue, but currently having that callback pattern as an option can sometimes lead to people (i.e. developers new to Lambda) to use it "accidentally" (meaning without understanding there are better patterns. So deprecating that callback pattern would mean one thing less to teach / advocate (against) about.
Firstly, thank you so much for your feedback!
Here are a few more details on why we're thinking about deprecating the callback handler:
-
Modernization. async/await has been introduced in ES2017 and Node.js has fully embraced it. If you're using a third-party package which uses async functions, you'll need to wrap it around promises and this adds complexity.
-
Simplicity. As mentioned by @astuyve and @aripalo, having multiple ways of defining a handler leads to confusion. You can achieve the same behavior using one or the other, so keeping both of them might be confusing.
I agree with the idea of deprecating the callback pattern.
One thing to consider is that communication might be more challenging than usual in this case. How can we ensure that all AWS examples, documentation, and repositories are properly updated to reflect this change?
Itβs probably a good idea to issue warnings well in advance, just in case anyone is still relying on this feature. A more cautious approach could be to introduce a deprecation warning in the logs whenever the callback is used, starting with the Node.js 24 runtime. It would be even better if this could also be applied to previously supported runtimes. The feature could then be fully removed in the next major runtime release, such as Node.js 26.
yessss
I agree! The async/await pattern has been the standard in modern JavaScript for years and is more intuitive and less error-prone than callback-based code.
I agree, should be deprecated!
There's literally no reason to support both ways. If people really want to use a callback pattern, they can easily just use the resolve callback in a returned Promise.
Aw man, ever since I taught myself Node.js, I've used callbacks. "Callback hell" never bothered me...oh well, I guess it's time I start learning the new ways.
Just please don't spam my logs by warning that this will happen on every single function invocation
think that setting
callbackWaitsForEmptyEventLoopto false is a way to perform compute after the handler exits
@astuyve This is true.
This AWS post from our good friend James Use Lambda response streaming section outlines code such as this for post response tasks. This works and is a valid.
export const handler = awslambda.streamifyResponse(
async (event, responseStream, _context) => {
const result = await myNormalLogic(event);
responseStream.setContentType("application/json");
responseStream.write(result);
responseStream.end();
await asyncTask(); // <-- like flushing metrics from an open telemetry observability tool
}
);However, what happens when I decide to use Elasticache Valkey Serverless... you know, on Lambda.
import { connectCache } from "cache.ts";
await connectCache(); // <-- π£ π£ π£
export const handler = awslambda.streamifyResponse(
async (event, responseStream, _context) => {
// same as above
}
);π£ π£ π£ The result would always be a function that errors when it eventually times out. The fix is one of two things:
- Set
context.callbackWaitsForEmptyEventLoop = falsein your handler. <-- 100% fix - Create new connections in your handler. <-- still testing edge cases
There could be other use cases, MySQL, etc where folks are doing things outside of the function handler where they are forced to use context.callbackWaitsForEmptyEventLoop. So I'm not sure dismissing these use cases are valid. So working for me is:
import { connectCache } from "cache.ts";
await connectCache();
export const handler = awslambda.streamifyResponse(
async (event, responseStream, _context) => {
context.callbackWaitsForEmptyEventLoop = false; // <- FIX
const result = await myNormalLogic(event);
responseStream.setContentType("application/json");
responseStream.write(result);
responseStream.end();
await asyncTask(); // <-- like flushing metrics from an open telemetry observability tool
}
);Like serious question too, what is happening here? Is it something with awslambda.streamifyResponse, the runtime, both? FYI, I'm using a custom Node image v24 image.
IMO top level await statements should still be supported. That was a huge performance boost for us when we were able to move that code out of the actual handler when migrating to ESM.
In case top level await statements rely on the callback signature I'd argue for keeping it π¬.
I'd love that something like the example @metaskills brought up would still work:
import { connectCache } from "cache.ts";
await connectCache(); // <-- Hopefully still works with node24 runtime π€πΌ
export const handler = async (event, context) => {
// ... handler logic
return { statusCode: 200, body: 'Success!' };
};API Gateway Lambda Authorizers require the use of a callback, if you return a promise they just don't work... will that be addressed prior to this change, or will lambda authorizers be stuck on Node.js 22?
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html
I think they're just an outdated examples, I've been using async/await with authorizers as far as I remember, and looking at these code snippets they just return a response.
Definitely deprecate it.
People either use it accidentally (due to old examples or documentation), or think that setting
callbackWaitsForEmptyEventLooptofalseis a way to perform compute after the handler exits (not realizing that those promises are only driven during subsequent invocations and can fail silently or dropped entirely on shutdown/timeout/suppressed init).
Hey , by saying people perhaps is you only, please limit you ability to comment when you know more!!
we currently having bad issues with this message thanks to all commenting, #137, Perhaps,first think about the effects to all those applications running until now!
@armordog The warning log message is not showing for all invokes but only at init time. You can also totally disable as described in the log itself by setting environment variable AWS_LAMBDA_NODEJS_DISABLE_CALLBACK_WARNING
@TuckerWhitehouse @dreamorosi
I can confirm that you can indeed write AWS Lambda Authorizer without callback. I've taken a note to update documentation to reflect this. Thanks for flagging this!
Here is a simple example:
export const handler = async (event, context) => {
console.log('Authorizer Event:', JSON.stringify(event, null, 2));
console.log('Authorizer Context:', JSON.stringify(context, null, 2));
return {
principalId: 'maxday',
policyDocument: {
Version: '2012-10-17',
Statement: [{
Action: 'execute-api:Invoke',
Effect: 'Allow',
Resource: event.routeArn
}]
},
context: {
userId: 'maxday',
requestTime: new Date().toISOString()
}
};
};@artisanvaultcode Thanks for reaching out, which issue are you facing? Are you referring to the warning message?
@maxday It still completely saturated our logging until we could deploy that environment variable.
Is there really no better way to communicate changes?
Can we have a global env var like AWS_LAMBDA_DONT_SPAM_ME that disables all future messages?
We were forced to use this callback style in our lambda due to Open Telemetry throwing errors. From our investigation, it attempts to use the lambda handler function instead of using the OTEL handler function. Is there a plan to ensure this works?
We were forced to use this callback style in our lambda due to Open Telemetry throwing errors. From our investigation, it attempts to use the lambda handler function instead of using the OTEL handler function. Is there a plan to ensure this works?
Could you share an example of what didn't work with OTEL and the Promise-based handler so we can test and check it?
Edit: This comment is out of date, me trying to do things quickly without double checking!
Currently if I try to export default instead of module.exports ...
2025-07-09T17:11:54.560Z undefined ERROR Uncaught Exception
{
"errorType": "Runtime.HandlerNotFound",
"errorMessage": "bundle.handler is undefined or not exported",
"stack": [
"Runtime.HandlerNotFound: bundle.handler is undefined or not exported",
" at UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1151:15)",
" at async start (file:///var/runtime/index.mjs:1332:23)",
" at async file:///var/runtime/index.mjs:1339:1"
]
}
So I may need more time to generate a reproduction.
We use esbuild to help transpile TypeScript to a JavaScript bundle.
Edit: Note that this is Open Telemetry throwing the error in the Init phase.
With the proper pointing to bundle.default, here is the actual error:
2025-07-09T17:16:46.436Z undefined ERROR Uncaught Exception
{
"errorType": "TypeError",
"errorMessage": "Cannot redefine property: default",
"stack": [
"TypeError: Cannot redefine property: default",
" at Function.defineProperty (<anonymous>)",
" at defineProperty (/opt/nodejs/node_modules/shimmer/index.js:14:10)",
" at wrap (/opt/nodejs/node_modules/shimmer/index.js:56:3)",
" at InstrumentationBase._wrap (/opt/nodejs/node_modules/@opentelemetry/instrumentation-aws-lambda/node_modules/@opentelemetry/instrumentation/build/src/platform/node/instrumentation.js:44:43)",
" at InstrumentationNodeModuleFile.patch (/opt/nodejs/node_modules/@opentelemetry/instrumentation-aws-lambda/build/src/instrumentation.js:99:26)",
" at /opt/nodejs/node_modules/@opentelemetry/instrumentation-aws-lambda/node_modules/@opentelemetry/instrumentation/build/src/platform/node/instrumentation.js:186:29",
" at Array.reduce (<anonymous>)",
" at AwsLambdaInstrumentation._onRequire (/opt/nodejs/node_modules/@opentelemetry/instrumentation-aws-lambda/node_modules/@opentelemetry/instrumentation/build/src/platform/node/instrumentation.js:176:46)",
" at onRequire (/opt/nodejs/node_modules/@opentelemetry/instrumentation-aws-lambda/node_modules/@opentelemetry/instrumentation/build/src/platform/node/instrumentation.js:230:29)",
" at Module.patchedRequire (/opt/nodejs/node_modules/require-in-the-middle/index.js:310:28)"
]
}
The code is this, compiled by esbuild:
const handler = async (event, context) => {
// Code is here.
};
export default handler;
How do you do this with lambda@edge?
Before:
exports.handler = (event: CloudFrontRequestEvent, context: Context, callback: CloudFrontRequestCallback) => {
const request = event.Records[0].cf.request;
if (!<some condition>) {
callback(null, request);
return;
}
}What do I do when I don't have the callback? Just return the request object? Edit: I tried this and it doesn't work. You can also in a viewer request function return either a response or a request to the callback and different things would happen. How do I distinguish between those two scenarios?
export const handler = async (event: CloudFrontRequestEvent) => {
const request = event.Records[0].cf.request;
if (!<some condition>) {
return request
}
}For the record, you need to update this documentation, as it only shows examples using the callback approach: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html
What is the signature of the return in this case. Is it Promise<CloudFrontRequest|CloudFrontResultResponse> ?
I would also like to add that you cannot suppress the warning on lambda@edge, since you cannot associate function versions that have environment variables (AWS_LAMBDA_NODEJS_DISABLE_CALLBACK_WARNING). This is causing severe spam to our distribution logs that I'm not sure how to avoid.
@maxday This has been very poorly implemented.
You're telling me I need to go through my 500+ lambdas and add an AWS_LAMBDA_NODEJS_DISABLE_CALLBACK_WARNING variable to each of them individually just to stop you spamming me with warnings?
Please reconsider the warnings.
Why do this, it has no tangible immediate benefit and causes us all to do massive code refactoring just to upgrade to latest nodejs version. The harm outweighs the good because users will avoid refactoring and instead run on outdated runtimes.
Could you please publish the logic or code used to determine whether it is "callback-based" or "async"?
Our code is not really callback based, however, we are still receiving a large volume of the "AWS Lambda plans to remove support for callback-based function handlers ..." warning messages. Is it a false positive? Could it be a bug in the logic determining which type of handler function it is?
If the logic or code can be published then we can check.
What is the point of removing callback handlers? Think of the amount of people you will affect, especially those who do not realize it when upgrading. Is there an optimization reason?
I don't want to sound harsh, but Amazon developers or engineers seem to make changes without at least proposing them to or asking those of us who are using their services! One of these days Amazon needs to face some consequences for this approach
@artisanvaultcode The subject of this issue is literally "feedback request" and the team said they "would love your thoughts"
@artisanvaultcode The subject of this issue is literally "feedback request" and the team said they "would love your thoughts"
They seem to have already decided to remove it, hence the constant warnings in logs.
100% YES
Could you please publish the logic or code used to determine whether it is "callback-based" or "async"?
Our code is not really callback based, however, we are still receiving a large volume of the "AWS Lambda plans to remove support for callback-based function handlers ..." warning messages. Is it a false positive? Could it be a bug in the logic determining which type of handler function it is?
If the logic or code can be published then we can check.
I also really want to see that!
Hi, can I please know the deadline for making these changes?
We are getting the warning that we need to upgrade, but our code is already using async/await. Can you tell us if our format will still be supported
module.exports.handler = async (event) => {
try {
// ... handler logic
return { statusCode: 200, body: 'Success!' };
} catch (errors) {
// error handling logic and return of user friendly error message
}
};
We are getting the warning that we need to upgrade, but our code is already using async/await. Can you tell us if our format will still be supported
module.exports.handler = async (event) => { try { // ... handler logic return { statusCode: 200, body: 'Success!' }; } catch (errors) { // error handling logic and return of user friendly error message } };
export const handler = async (event) => {
try {
// ... handler logic
return { statusCode: 200, body: 'Success!' };
} catch (errors) {
// error handling logic and return of user friendly error message
}
};
We are getting the warning that we need to upgrade, but our code is already using async/await. Can you tell us if our format will still be supported
module.exports.handler = async (event) => { try { // ... handler logic return { statusCode: 200, body: 'Success!' }; } catch (errors) { // error handling logic and return of user friendly error message } };export const handler = async (event) => { try { // ... handler logic return { statusCode: 200, body: 'Success!' }; } catch (errors) { // error handling logic and return of user friendly error message } };
Thanks for your reply. Are you saying that module.exports.handler is no longer supported? The problem with this is that, as I understand it, export is only useable by esm modules and we are still using commonJS. This would require a rewrite of our codebase to support esm before we could upgrade to node 24. This is not a reasonable solution for us.
We are getting the warning that we need to upgrade, but our code is already using async/await. Can you tell us if our format will still be supported
module.exports.handler = async (event) => { try { // ... handler logic return { statusCode: 200, body: 'Success!' }; } catch (errors) { // error handling logic and return of user friendly error message } };export const handler = async (event) => { try { // ... handler logic return { statusCode: 200, body: 'Success!' }; } catch (errors) { // error handling logic and return of user friendly error message } };Thanks for your reply. Are you saying that
module.exports.handleris no longer supported? The problem with this is that, as I understand it, export is only useable by esm modules and we are still using commonJS. This would require a rewrite of our codebase to support esm before we could upgrade to node 24. This is not a reasonable solution for us.
I don't know, but that's what the example says. And in that case just stay on 22. You have until June 1, 2027 before AWS won't allow you to create Node 22 functions: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
Here is the code we're using to detect whether or not the handler is async: https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/main/src/UserFunction.js#L314-L325
@maxday This is very helpful. Thank you.
I am not sure whether that function has test cases, maybe it doesn't have.
The function checks handler.constructor.name === 'AsyncFunction' which is not reliable.
The code below shows why it is not reliable:
const funcs = [
async function myfunc() {
return null;
},
async () => 'ad',
function xyz() {
return Promise.resolve('aa');
},
() => Promise.resolve('aaa'),
];
for (const f of funcs)
console.log(f.constructor.name);
The outputs are "AsyncFunction","AsyncFunction","Function","Function".
Apparently the code has bug https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/blob/main/src/UserFunction.js#L314-L325
That is why it yields so many false alarms.
lol, why are we doing this again? The lambda team does not need to make prescriptive stylistic breaking changes that will prevent users from using the latest most secure runtime versions as soon as possible. This is not wanted or needed, please remove this apparently buggy logic that is annoying all of us.
I don't think this is a good idea, the callback api allows us to have more control over the very specific execution model of AWS Lambda, so it does make little sense to give up this imperative API in favor to something less powerful. For example it's very handy in an event driven architecture, while the async/await API would require to juggle with promises for no reason at all.
With all due respect, why making a breaking change for no apparent reason?
Can you give a couple examples of what you can do with the old API that you can't do with the promise-based one?
Can you give a couple examples of what you can do with the old API that you can't do with the promise-based one?
Not be forced to make needless maintenance changes to get the latest security updates, or maybe youd like to implement your lambdas with a turing machine since after all it's turing complete.
Not sure your answer addresses my question, but your disagreement with the change is noted.
The reason why I'm asking is that I want to make sure every use case is covered. If there's something you can't do with the promise-based API that you could do with the callback-one, I'd like to know, so we can consider it and/or suggest alternatives.
to get the latest security updates
Just to clarify, for the sake of other readers:
Node.js 22 as a runtime will be in maintenance mode until 2027-04-30 according to the official release schedule and will be supported by AWS as managed runtime for AWS Lambda until that same date as described here.
While a runtime is in maintenance mode, it continues to get critical bug fixes and security updates.
to get the latest security updates
Just to clarify, for the sake of other readers:
Node.js 22 as a runtime will be in maintenance mode until 2027-04-30 according to the official release schedule and will be supported by AWS as managed runtime for AWS Lambda until that same date as described here.
While a runtime is in maintenance mode, it continues to get critical bug fixes and security updates.
I apologize to be a bit snarky, but am annoyed at framework developers trying to enforce disruptive stylistic changes in their frameworks. Let us decide which style is best and quit trying to be prescriptive. And as you well know AWS does not indefinitely support legacy runtimes and eventually they stop getting security patches and users will be forced to perform maintenance on lambdas which have been running without changes for years.
AWS doc is still filled with lots of examples with callback style functions.
Maybe it would be nice to update these docs before flooding logs with warnings ?
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html
Can you give a couple examples of what you can do with the old API that you can't do with the promise-based one?
Sorry for the late response, did not see the notification.
It would be hard for me to provide an example from the code I was working on a couple of weeks ago.
I think you can do everything with the Promise API that you would do with callbacks, but the problem is that it plagues the code with async/await for no reason and the event loop treats promises differently than function calls requiring to always be very careful how you handle them. It only adds complexity that really no one calls for.
You have no idea how bad of an idea it is to remove callback support. All of the tutorials, blog posts, stackoverflow posts, existing code that is out there will no longer work. Not to mention all of the code updating and inevitable function breaking when people don't realize 24 doesn't support callbacks. This has to be one of the worst ideas from AWS and I wont be surprised if you end up reversing this completely pointless change.
Hi, thanks for this warning, and for lambda in general, it's very valuable to us. π
The two reasons given in this comment don't strike me as sufficient to make the proposed change:
Are there any other (hopefully better) reasons?
Notably, we have working lambdas that this proposed change will break (when we are eventually forced to update them to a newer node runtime).
As others have mentioned, the first step toward encouraging users to use the promise style over the callback style would be to update the documentation (https://www.reddit.com/r/technicalwriting/comments/1oicnxi/aws_tech_writers_majorly_impacted_by_todays/), not to threaten to break peoples code. (:
I disagree with this change. It will involve us in a lot of unnecessary maintenance changes that do not provide any business values.
I want to spend expensive programming resources on providing business value rather than technical tidy up.
The onus should be framework team point to any actual harms that leaving it alone would result in. What is the ROI of this change for US.
The team might like to reflect the amount of effort involved in getting their own house in order (documentation, example code) BEFORE enforcing it on the rest of us.