Bug Report #50075: Quarkus AWS Lambda API Gateway v2 - Multiple Set-Cookie Headers Not Handled Correctly
When using Quarkus as an AWS Lambda handler behind API Gateway v2, multiple Set-Cookie
headers are incorrectly merged into a single header in the Lambda response. This causes browsers to fail to set multiple cookies as expected, breaking applications that rely on more than one cookie.
-
Build the Lambda function:
./gradlew build -Dquarkus.package.jar.enabled=true -Dquarkus.package.jar.type=legacy-jar -Dquarkus.native.enabled=false
-
Invoke the Lambda function locally:
sam local invoke --template ./build/sam.jvm.yaml -e ./example-gateway-event.json
-
(Optional) Debug:
sam local invoke --template ./build/sam.jvm.yaml -e ./example-gateway-event.json --debug-port 5858
Then connect your IDE to
localhost:5858
.
The response from PingController merges all cookies into a single Set-Cookie
header:
{
"statusCode": 200,
"headers": {
"content-length": "40",
"Set-Cookie": "cookie1=value1;Version=1;Path=/;Max-Age=3600;Secure;HttpOnly;SameSite=Lax,cookie2=value2;Version=1;Path=/;Max-Age=3600;Secure;HttpOnly;SameSite=Lax",
"Content-Type": "application/json"
},
"multiValueHeaders": null,
"cookies": null,
"body": "{\"status\":\"ok\",\"rootPath\":\"Hello hello\"}",
"isBase64Encoded": false
}
As a result, when deployed to AWS, browsers do not set multiple cookies correctly if more than one is present.
The response should use the cookies
array field as per API Gateway v2 Lambda integration, with each cookie as a separate entry:
{
"statusCode": 200,
"headers": {
"content-length": "40",
"Content-Type": "application/json"
},
"multiValueHeaders": null,
"cookies": [
"cookie1=value1;Version=1;Path=/;Max-Age=3600;Secure;HttpOnly;SameSite=Lax",
"cookie2=value2;Version=1;Path=/;Max-Age=3600;Secure;HttpOnly;SameSite=Lax"
],
"body": "{\"status\":\"ok\",\"rootPath\":\"Hello hello\"}",
"isBase64Encoded": false
}
With this format, browsers correctly set multiple cookies.
The bug is caused by the following logic in LambdaHttpHandler:
// Handle cookies separately to preserve commas in the header values
@Override
public void handleMessage(Object msg) {
try {
// ...existing code...
if (msg instanceof HttpResponse res) {
responseBuilder.setStatusCode(res.status().code());
final Map<String, String> headers = new HashMap<>();
responseBuilder.setHeaders(headers);
for (String name : res.headers().names()) {
final List<String> allForName = res.headers().getAll(name);
if (allForName == null || allForName.isEmpty()) {
continue;
}
// Handle cookies separately to preserve commas in the header values
if ("set-cookie".equals(name)) {
responseBuilder.setCookies(allForName);
continue;
}
// ...
}
}
}
}
However, in the Lambda runtime context, the msg
is of type io.vertx.core.http.impl.AssembledFullHttpResponse
, and its headers are an instance of io.vertx.core.http.impl.headers.HeadersMultiMap
, which is case-insensitive and converts all header names to Camel-Kebab-Case. Thus, the check for "set-cookie"
fails because the actual header name is "Set-Cookie"
.
Change the header name check to be case-insensitive:
if ("set-cookie".equalsIgnoreCase(name)) {
responseBuilder.setCookies(allForName);
continue;
}
This bug report was generated from a minimal reproduction project: example-bug-quarkus-aws-lambda-gateway-v2-multiple-cookies-broken