AWS Lambda
The quarkus-amazon-lambda
extension allows you to use Quarkus to build your AWS Lambdas.
Your lambdas can use injection annotations from CDI or Spring and other Quarkus facilities as you need them.
Quarkus lambdas can be deployed using the Amazon Java Runtime, or you can build a native executable and use Amazon’s Custom Runtime if you want a smaller memory footprint and faster cold boot startup time.
Quarkus’s integration with lambdas also supports Quarkus’s Live Coding development cycle. You can bring up your Quarkus lambda project in dev or test mode and code on your project live.
Prerequisites
To complete this guide, you need:
-
Roughly 30 minutes
-
An IDE
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.9
-
Optionally the Quarkus CLI if you want to use it
-
Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)
-
AWS SAM CLI, for local testing
For Gradle projects please see below, or for further reference consult the guide in the Gradle setup page. |
Getting Started
This guide walks you through generating an example Java project via a maven archetype and deploying it to AWS.
Installing AWS bits
Installing all the AWS bits is probably the most difficult thing about this guide. Make sure that you follow all the steps for installing AWS CLI.
Creating the Maven Deployment Project
Create the Quarkus AWS Lambda maven project using our Maven Archetype.
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-archetype \
-DarchetypeVersion=3.17.5
If you prefer to use Gradle, you can quickly and easily generate a Gradle project via code.quarkus.io
adding the Copy the build.gradle, gradle.properties and settings.gradle into the above-generated Maven archetype project, to follow along with this guide. Execute: gradle wrapper to set up the gradle wrapper (recommended). For full Gradle details, see the Gradle build section below. |
Choose Your Lambda
The quarkus-amazon-lambda
extension scans your project for a class that directly implements the Amazon RequestHandler<?, ?>
or RequestStreamHandler
interface.
It must find a class in your project that implements this interface, or it will throw a build time failure.
If it finds more than one handler class, a build time exception will also be thrown.
Sometimes, though, you might have a few related lambdas that share code and creating multiple maven modules is just
an overhead you don’t want to do. The quarkus-amazon-lambda
extension allows you to bundle multiple lambdas in one
project and use configuration or an environment variable to pick the handler you want to deploy.
The generated project has three lambdas within it. Two that implement the RequestHandler<?, ?>
interface, and one that implements the RequestStreamHandler
interface. One that is used and two that are unused. If you open up
src/main/resources/application.properties
you’ll see this:
quarkus.lambda.handler=test
The quarkus.lambda.handler
property tells Quarkus which lambda handler to deploy. This can be overridden
with an environment variable too.
If you look at the three generated handler classes in the project, you’ll see that they are @Named
differently.
@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {
}
@Named("unused")
public class UnusedLambda implements RequestHandler<InputObject, OutputObject> {
}
@Named("stream")
public class StreamLambda implements RequestStreamHandler {
}
The CDI name of the handler class must match the value specified within the quarkus.lambda.handler
property.
Deploy to AWS Lambda Java Runtime
There are a few steps to get your lambda running on AWS. The generated maven project contains a helpful script to create, update, delete, and invoke your lambdas for pure Java and native deployments.
Build and Deploy
Build the project:
quarkus build
./mvnw install
./gradlew build
This will compile and package your code.
Create an Execution Role
View the Getting Started Guide for deploying
a lambda with AWS CLI. Specifically, make sure you have created an Execution Role
. You will need to define
a LAMBDA_ROLE_ARN
environment variable in your profile or console window, Alternatively, you can edit
the manage.sh
script that is generated by the build and put the role value directly there:
LAMBDA_ROLE_ARN="arn:aws:iam::1234567890:role/lambda-role"
Extra Build Generated Files
After you run the build, there are a few extra files generated by the quarkus-amazon-lambda
extension. These files
are in the build directory: target/
for maven, build/
for gradle.
-
function.zip
- lambda deployment file -
manage.sh
- wrapper around aws lambda cli calls -
bootstrap-example.sh
- example bootstrap script for native deployments -
sam.jvm.yaml
- (optional) for use with sam cli and local testing -
sam.native.yaml
- (optional) for use with sam cli and native local testing
Create the function
The target/manage.sh
script is for managing your lambda using the AWS Lambda Java runtime. This script is provided only for
your convenience. Examine the output of the manage.sh
script if you want to learn what aws commands are executed
to create, delete, and update your lambdas.
manage.sh
supports four operation: create
, delete
, update
, and invoke
.
To verify your setup, that you have the AWS CLI installed, executed aws configure for the AWS access keys,
and set up the LAMBDA_ROLE_ARN environment variable (as described above), please execute manage.sh without any parameters.
A usage statement will be printed to guide you accordingly.
|
If using Gradle, the path to the binaries in the manage.sh must be changed from target to build
|
To see the usage
statement, and validate AWS configuration:
sh target/manage.sh
You can create
your function using the following command:
sh target/manage.sh create
or if you do not have LAMBDA_ROLE_ARN
already defined in this shell:
LAMBDA_ROLE_ARN="arn:aws:iam::1234567890:role/lambda-role" sh target/manage.sh create
Do not change the handler switch. This must be hardcoded to io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest . This
handler bootstraps Quarkus and wraps your actual handler so that injection can be performed.
|
If there are any problems creating the function, you must delete it with the delete
function before re-running
the create
command.
sh target/manage.sh delete
Commands may also be stacked:
sh target/manage.sh delete create
Invoke the Lambda
Use the invoke
command to invoke your function.
sh target/manage.sh invoke
The example lambda takes input passed in via the --payload
switch which points to a json file
in the root directory of the project.
The lambda can also be invoked locally with the SAM CLI like this:
sam local invoke --template target/sam.jvm.yaml --event payload.json
If you are working with your native image build, simply replace the template name with the native version:
sam local invoke --template target/sam.native.yaml --event payload.json
Update the Lambda
You can update the Java code as you see fit. Once you’ve rebuilt, you can redeploy your lambda by executing the
update
command.
sh target/manage.sh update
Deploy to AWS Lambda Custom (native) Runtime
If you want a lower memory footprint and faster initialization times for your lambda, you can compile your Java
code to a native executable. Just make sure to rebuild your project with the -Dnative
switch.
For Linux hosts, execute:
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
If you are building on a non-Linux system, you will need to also pass in a property instructing Quarkus to use a docker build as Amazon
Lambda requires linux binaries. You can do this by passing this property to your build:
-Dquarkus.native.container-build=true . This requires you to have Docker installed locally, however.
|
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
./mvnw install -Dnative -DskipTests -Dquarkus.native.container-build=true
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.container-build=true
Either of these commands will compile and create a native executable image. It also generates a zip file target/function.zip
.
This zip file contains your native executable image renamed to bootstrap
. This is a requirement of the AWS Lambda
Custom (Provided) Runtime.
The instructions here are exactly as above with one change: you’ll need to add native
as the first parameter to the
manage.sh
script:
sh target/manage.sh native create
As above, commands can be stacked. The only requirement is that native
be the first parameter should you wish
to work with native image builds. The script will take care of the rest of the details necessary to manage your native
image function deployments.
Examine the output of the manage.sh
script if you want to learn what aws commands are executed
to create, delete, and update your lambdas.
One thing to note about the create command for native is that the aws lambda create-function
call must set a specific environment variable:
--environment 'Variables={DISABLE_SIGNAL_HANDLERS=true}'
Examine the POM and Gradle build
There is nothing special about the POM other than the inclusion of the quarkus-amazon-lambda
extension
as a dependency. The extension automatically generates everything you might need for your lambda deployment.
In previous versions of this extension, you had to set up your pom or gradle to zip up your executable for native deployments, but this is not the case anymore. |
Gradle build
Similarly, for Gradle projects, you also just have to add the quarkus-amazon-lambda
dependency. The extension automatically generates everything you might need
for your lambda deployment.
Example Gradle dependencies:
dependencies {
implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
implementation 'io.quarkus:quarkus-resteasy'
implementation 'io.quarkus:quarkus-amazon-lambda'
testImplementation 'io.quarkus:quarkus-junit5'
testImplementation 'io.rest-assured:rest-assured'
}
Live Coding and Unit/Integration Testing
To mirror the AWS Lambda environment as closely as possible in a dev environment, the Quarkus AWS Lambda extension boots up a mock AWS Lambda event server in Quarkus Dev and Test mode. This mock event server simulates a true AWS Lambda environment.
While running in Quarkus Dev Mode, you can feed events to it by doing an HTTP POST to http://localhost:8080
.
The mock event server will receive the events and your lambda will be invoked. You can perform live coding on your lambda
and changes will automatically be recompiled and available the next invocation you make. Here’s an example:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
$ curl -d "{\"name\":\"John\"}" -X POST http://localhost:8080
For your unit tests, you can also invoke on the mock event server using any HTTP client you want. Here’s an example using rest-assured. Quarkus starts up a separate Mock Event server under port 8081. The default port for Rest Assured is automatically set to 8081 by Quarkus, so you can invoke on this endpoint.
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;
@QuarkusTest
public class LambdaHandlerTest {
@Test
public void testSimpleLambdaSuccess() throws Exception {
Person in = new Person();
in.setName("Stu");
given()
.contentType("application/json")
.accept("application/json")
.body(in)
.when()
.post()
.then()
.statusCode(200)
.body(containsString("Hello Stu"));
}
}
The mock event server is also started for @QuarkusIntegrationTest
tests so will work
with native binaries too. All this provides similar functionality to the SAM CLI local testing, without the overhead of Docker.
Finally, if port 8080 or port 8081 is not available on your computer, you can modify the dev and test mode ports with application.properties
quarkus.lambda.mock-event-server.dev-port=8082
quarkus.lambda.mock-event-server.test-port=8083
A port value of zero will result in a randomly assigned port.
To turn off the mock event server:
quarkus.lambda.mock-event-server.enabled=false
Testing with the SAM CLI
If you do not want to use the mock event server, you can test your lambdas with SAM CLI.
The AWS SAM CLI allows you to run your lambdas locally on your laptop in a simulated Lambda environment. This requires docker to be installed. This is an optional approach should you choose to take advantage of it. Otherwise, the Quarkus JUnit integration should be sufficient for most of your needs.
A starter template has been generated for both JVM and native execution modes.
Run the following SAM CLI command to locally test your lambda function, passing the appropriate SAM template
.
The event
parameter takes any JSON file, in this case the sample payload.json
.
If using Gradle, the path to the binaries in the YAML templates must be changed from target to build
|
sam local invoke --template target/sam.jvm.yaml --event payload.json
The native image can also be locally tested using the sam.native.yaml
template:
sam local invoke --template target/sam.native.yaml --event payload.json
Modifying function.zip
There are times when you may have to add some additions to the function.zip
lambda deployment that is generated
by the build. To do this, create a zip.jvm
or zip.native
directory within src/main
.
Create zip.jvm/
if you are doing a pure Java lambda. zip.native/
if you are doing a native deployment.
Any you files and directories you create under your zip directory will be included within function.zip
Custom bootstrap
script
There are times you may want to set a specific system properties or other arguments when lambda invokes
your native quarkus lambda deployment. If you include a bootstrap
script file within
zip.native
, the quarkus extension will automatically rename the executable to runner
within
function.zip
and set the unix mode of the bootstrap
script to executable.
The native executable must be referenced as runner if you include a custom bootstrap script.
|
The extension generates an example script within target/bootstrap-example.sh
.
Tracing with AWS XRay and GraalVM
If you are building native images, and want to use AWS X-Ray Tracing with your lambda
you will need to include quarkus-amazon-lambda-xray
as a dependency in your pom. The AWS X-Ray
library is not fully compatible with GraalVM, so we had to do some integration work to make this work.
In addition, remember to enable the AWS X-Ray tracing parameter in manage.sh
, in the cmd_create()
function. This can also be set in the AWS Management Console.
--tracing-config Mode=Active
For the sam template files, add the following to the YAML function Properties.
Tracing: Active
AWS X-Ray does add many classes to your distribution, do ensure you are using at least the 256MB AWS Lambda memory size.
This is explicitly set in manage.sh
cmd_create()
. Whilst the native image potentially can always use a lower memory setting, it would be recommended to keep the setting the same, especially to help compare performance.
Using HTTPS or SSL/TLS
If your code makes HTTPS calls (e.g. to a microservice, to an AWS service), you will need to add configuration to the native image, as GraalVM will only include the dependencies when explicitly declared. Quarkus, by default enables this functionality on extensions that implicitly require it. For further information, please consult the Quarkus SSL guide
Open src/main/resources/application.properties and add the following line to enable SSL in your native image.
quarkus.ssl.native=true
Using the AWS Java SDK v2
Quarkus now has extensions for DynamoDB, S3, SNS and SQS (more coming). Please check those guides on how to use the various AWS Services with Quarkus, as opposed to wiring manually like below. |
With minimal integration, it is possible to leverage the AWS Java SDK v2, which can be used to invoke services such as SQS, SNS, S3 and DynamoDB.
For native image, however, the URL Connection client must be preferred over the Apache HTTP Client when using synchronous mode, due to issues in the GraalVM compilation (at present).
Add quarkus-jaxb
as a dependency in your Maven pom.xml
, or Gradle build.gradle
file.
You must also force your AWS service client for SQS, SNS, S3 et al., to use the URL Connection client, which connects to AWS services over HTTPS, hence the inclusion of the SSL enabled property, as described in the Using HTTPS or SSL/TLS section above.
// select the appropriate client, in this case SQS, and
// insert your region, instead of XXXX, which also improves startup time over the default client
client = SqsClient.builder().region(Region.XXXX).httpClient(software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.builder().build()).build();
For Maven, add the following to your pom.xml
.
<properties>
<aws.sdk2.version>2.10.69</aws.sdk2.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.sdk2.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>url-connection-client</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<!-- sqs/sns/s3 etc -->
<artifactId>sqs</artifactId>
<exclusions>
<!-- exclude the apache-client and netty client -->
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
</exclusion>
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>commons-logging-jboss-logging</artifactId>
</dependency>
</dependencies>
if you see java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty or similar SSL error, due to the current status of GraalVM,
there is some additional work to bundle the function.zip , as below. For more information, please see the Quarkus Native SSL Guide.
|
Additional requirements for client SSL
The native executable requires some additional steps to enable client SSL that S3 and other AWS libraries need.
-
A custom
bootstrap
script -
libsunec.so
must be added tofunction.zip
-
cacerts
must be added tofunction.zip
To do this, first create a directory src/main/zip.native/
with your build. Next create a shell script file called bootstrap
within src/main/zip.native/
, like below. An example is created automatically in your build folder (target or build), called bootstrap-example.sh
#!/usr/bin/env bash
./runner -Djava.library.path=./ -Djavax.net.ssl.trustStore=./cacerts
Additional set -Djavax.net.ssl.trustStorePassword=changeit
if your cacerts
file is password protected.
Next you must copy some files from your GraalVM distribution into src/main/zip.native/
.
GraalVM versions can have different paths for these files whether you are using the Java 8 or 11 version. Adjust accordingly. |
cp $GRAALVM_HOME/lib/libsunec.so $PROJECT_DIR/src/main/zip.native/
cp $GRAALVM_HOME/lib/security/cacerts $PROJECT_DIR/src/main/zip.native/
Now when you run the native build all these files will be included within function.zip
If you are using a Docker image to build, then you must extract these files from this image. |
To extract the required ssl, you must start up a Docker container in the background, and attach to that container to copy the artifacts.
First, let’s start the GraalVM container, noting the container id output.
docker run -it -d --entrypoint bash quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21
# This will output a container id, like 6304eea6179522aff69acb38eca90bedfd4b970a5475aa37ccda3585bc2abdde
# Note this value as we will need it for the commands below
First, libsunec.so
, the C library used for the SSL implementation:
docker cp {container-id-from-above}:/opt/graalvm/lib/libsunec.so src/main/zip.native/
Second, cacerts
, the certificate store. You may need to periodically obtain an updated copy, also.
docker cp {container-id-from-above}:/opt/graalvm/lib/security/cacerts src/main/zip.native/
Your final archive will look like this:
jar tvf target/function.zip
bootstrap
runner
cacerts
libsunec.so
Deploy to AWS Lambda using a Container Image
AWS Lambda supports creating your lambdas by referencing container images rather than uploading ZIP files. This can have some benefits such as bypassing the size limit of the uploaded ZIP files. You can define lambda functions for both native builds and regular JVM builds.
JVM container image
For a regular JVM distribution you need to base your image off the official AWS Java base images. Below is an example of a Dockerfile that would create a container image from your Quarkus Lambda project. It assumes that mvn package
has been executed and binaries are available in the target/
directory:
FROM public.ecr.aws/lambda/java:11
ADD target/my-service-0.0.1-SNAPSHOT-runner.jar /var/task/lib/my-service.jar
ADD target/lib/ /var/task/lib/
CMD ["io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest"]
Native executable container image
To create a lambda container image that uses the native executable we’ll need to do things a little differently. In this case, we won’t need to use the java:11
base image from AWS, but instead we’ll use a special image that assumes that the runtime environment for the lambda is provided. The example below creates such a container. It assumes that a Maven build has been executed (such as mvn package -Dnative=true
) and has generated the native binary into the target/
directory. The binary needs to be named bootstrap
and be placed in /var/runtime/
:
FROM public.ecr.aws/lambda/provided
ADD target/my-service-0.0.1-SNAPSHOT-runner /var/runtime/bootstrap
RUN chmod ugo+x /var/runtime/bootstrap
CMD ["io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest"]
Deploying a container image lambda
Below, you can see how the container images created above can be built and deployed to AWS using the docker
and aws
command line tools. These instructions work for both native and jvm container images and assume that the aws
command line tool has been logged in.
Build the Docker image
# Assuming we are located in the root directory of the project and created a Dockerfile there
docker build .
[output omitted]
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:[SOME SHA] 0.0s
Create an ECR repository in the users AWS account
aws ecr create-repository --repository-name my/test/quarkus-lambda
Tag the image using your ECR registry information
docker tag [SOME SHA] [YOUR AWS ACCOUNT ID].dkr.ecr.[YOUR AWS ACCOUNT REGION].amazonaws.com/my/test/quarkus-lambda:v1
Log Docker into your ECR registry and push the Docker image to it
aws ecr get-login-password --region region | docker login --username AWS --password-stdin [YOUR AWS ACCOUNT ID].dkr.ecr.[YOUR AWS ACCOUNT REGION].amazonaws.com
docker push [YOUR AWS ACCOUNT ID].dkr.ecr.[YOUR AWS ACCOUNT REGION].amazonaws.com/my/test/quarkus-lambda:v1
Create the AWS lambda function with the AWS CLI tool
Make sure you reference the image you uploaded previously (assumes that a role exists that can be used to run the lambda). Please note that it is not unlikely that for the JVM lambda function, the default memory limit of 128Mb
will not be enough to run the function. In that case, you can increase the memory limit when creating the function by providing the --memory-size 256
parameter to your aws lambda create-function
command. You can also adjust the function in the AWS console after you’ve created it.
aws lambda create-function --function-name my-test-quarkus-lambda-function --package-type Image --code ImageUri=[YOUR AWS ACCOUNT ID].dkr.ecr.[YOUR AWS ACCOUNT REGION].amazonaws.com/my/test/quarkus-lambda:v1 --role arn:aws:iam::[YOUR AWS ACCOUNT ID]:role/[SOME ROLE]
Now you can use the AWS console to view and test your new lambda function.
Amazon Alexa Integration
To use Alexa with Quarkus native, you need to use the Quarkus Amazon Alexa extension hosted at the Quarkiverse Hub.
<dependency>
<groupId>io.quarkiverse.alexa</groupId>
<artifactId>quarkus-amazon-alexa</artifactId>
<version>${quarkus-amazon-alexa.version}</version> (1)
</dependency>
1 | Define the latest version of the extension in your POM file. |
Create your Alexa handler, as normal, by sub-classing the abstract com.amazon.ask.SkillStreamHandler
, and add your request handler implementation.
That’s all there is to it!
SnapStart
To optimize your application for Lambda SnapStart, check the SnapStart Configuration Documentation.