Deploying Java Serverless Functions as AWS Lambda
Learn about SAM (superset of CloudFormation) including some special commands and shortcuts to ease Java serverless code development, testing, and deployment.
Join the DZone community and get the full member experience.
Join For FreeThe AWS console allows the user to create and update cloud infrastructure resources in a user-friendly manner. Despite all the advantages that such a high-level tool might have, using it is repetitive and error-prone. For example, each time we create a Lambda function using the AWS console, we need to repeat the same operations again and again, and, even if these operations are intuitive and easy as graphical widget manipulations, the whole process is time-consuming and laborious. This working mode is convenient for rapid prototyping, but as soon as we have to work on a real project with a relatively large scope and duration, it doesn't meet the team's goals and wishes anymore. In such a case, the preferred solution is IaC (Infrastructure as Code).
IaC essentially consists of using a declarative notation in order to specify infrastructure resources. In the case of AWS, this notation expressed as a formalism based on a JSON or YAML syntax is captured in configuration files and submitted to the CloudFormation IaC utility.
CloudFormation is a vast topic that couldn't be detailed in a blog ticket. The important point that we need to retain here is that this service is able to process input configuration files and guarantee the creation and the update of the associated AWS cloud infrastructure resources. While the benefits of the CloudFormation IaC approach are obvious, this tool has a reputation for being verbose, unwieldy, and inflexible. Fortunately, AWS Lambda developers have the choice of using SAM, a superset of CloudFormation which includes some special commands and shortcuts aiming at easing the development, testing, and deployment of the Java serverless code.
Installing SAM
Installing SAM is very simple: one only has to follow the guide. For example, installing it on Ubuntu 22.04 LTS is as simple as shown below:
$ sudo apt-get update
...
$ sudo apt-get install awscli
...
$ aws --version
aws-cli/2.9.12 Python/3.9.11 Linux/5.15.0-57-generic exe/x86_64.ubuntu.22 prompt/off
Creating AWS Lambda Functions in Java With SAM
Now that SAM is installed on your workstation, you can write and deploy your first Java serverless function. Of course, we assume here that your AWS account has been created and that your environment is configured such that you can run AWS CLI commands.
Like CloudFormation, SAM is based on the notion of template, which is a YAML-formatted text file that describes an AWS infrastructure. This template file, which named by default is template.yaml
, has to be authored manually, such that to be aligned with the SAM template anatomy (complete specifications can be found here). But writing a template.yaml
file from scratch is difficult; hence, the idea of automatically generating it. Enters CookieCutter.
CookieCutter is an open-source project allowing automatic code generation. It is greatly used in the Python world but here, we'll use it in the Java world. Its modus operandi is very similar to one of the Maven archetypes, in the sense that it is able to automatically generate full Java projects, including but not limited to packages, classes, configuration files, etc. The generation process is highly customizable and is able to replace string occurrences, flagged by placeholders expressed in a dedicated syntax, by values defined in an external JSON formatted file.
This GitHub repository provides such a CookieCutter-based generator able to generate a simple but complete Java project, ready to be deployed as an AWS Lambda serverless function. The listing below shows how:
$ sam init --location https://github.com/nicolasduminil/sam-template
You've downloaded /home/nicolas/.cookiecutters/sam-template before. Is it okay to delete and re-download it? [yes]:
project_name [my-project-name]: aws-lambda-simple
aws_lambda_resource_name [my-aws-lambda-resource-name]: AwsLambdaSimple
java_package_name [fr.simplex_software.aws.lambda.functions]:
java_class_name [MyAwsLambdaClassName]: AwsLambdaSimple
java_handler_method_name [handleRequest]:
maven_group_id [fr.simplex-software.aws.lambda]:
maven_artifact_id [my-aws-function]: aws-lambda-simple
maven_version [1.0.0-SNAPSHOT]:
function_name [AwsLambdaTestFunction]: AwsLambdaSimpleFunction
Select architecture:
1 - arm64
2 - x86_64
Choose from 1, 2 [1]:
timeout [10]:
Select tracing:
1 - Active
2 - Passthrough
Choose from 1, 2 [1]:
sam init
above mentions the location of the CookieCutter-based template used to generate a new Java project. This generation process takes the form of a dialog where the utility is asking questions and accepting answers. Each question has default responses and, in order to accept them, the user just needs to type Enter.
Everything starts by asking about the project name and we chose aws-lambda-simple
. Further information to be entered is:
- AWS resource name
-
Maven GAV (GroupId, ArtifactId, Version)
-
Java package name
-
Java class name
-
Processor architecture
-
Timeout value
-
Tracing profile
$ cd aws-lambda-simple/
nicolas@nicolas-XPS-13-9360:~/sam-test/aws-lambda-simple$ mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ----------< fr.simplex-software.aws.lambda:aws-lambda-simple >----------
[INFO] Building aws-lambda-simple 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ aws-lambda-simple ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/nicolas/sam-test/aws-lambda-simple/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ aws-lambda-simple ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/nicolas/sam-test/aws-lambda-simple/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ aws-lambda-simple ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/nicolas/sam-test/aws-lambda-simple/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ aws-lambda-simple ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ aws-lambda-simple ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ aws-lambda-simple ---
[INFO] Building jar: /home/nicolas/sam-test/aws-lambda-simple/target/aws-lambda-simple-1.0.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-shade-plugin:3.2.1:shade (default) @ aws-lambda-simple ---
[INFO] Replacing /home/nicolas/sam-test/aws-lambda-simple/target/aws-lambda-simple.jar with /home/nicolas/sam-test/aws-lambda-simple/target/aws-lambda-simple-1.0.0-SNAPSHOT-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.604 s
[INFO] Finished at: 2023-01-12T19:09:23+01:00
[INFO] ------------------------------------------------------------------------
Our new Java project has been built and packaged as a JAR (Java ARchive). The generated template.yaml
file defines the required AWS cloud infrastructure, as shown below:
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: aws-lambda-simple
Resources:
AwsLambdaSimple:
Type: AWS::Serverless::Function
Properties:
FunctionName: AwsLambdaSimpleFunction
Architectures:
- arm64
Runtime: java11
MemorySize: 128
Handler: fr.simplex_software.aws.lambda.functions.AwsLambdaSimple::handleRequest
CodeUri: target/aws-lambda-simple.jar
Timeout: 10
Tracing: Active
This file has been created based on the value entered during the generation process. Things like the AWS template version and the transformation version are constants and should be used as such. All the other elements are known as they mirror the input data. Special consideration has to be given to the CodeUri
element which specifies the location of the JAR to be deployed as the Lambda function. It contains the class AwsLambdaSimple
below:
public class AwsLambdaSimple
{
private static Logger log = Logger.getLogger(AwsLambdaSimple.class.getName());
public String handleRequest (Map<String, String> event)
{
log.info("*** AwsLambdaSimple.handleRequest: Have received: " + event);
return event.entrySet().stream().map(e -> e.getKey() + "->" + e.getValue()).collect(Collectors.joining(","));
}
}
A Lambda function in Java can be run in the following two modes:
- A synchronous or
RequestResponse
mode in which the caller waits for whatever response the Lambda function returns - An asynchronous or
Event
mode in which the caller is responded to without waiting, by the Lambda platform itself, while the function proceeds with the request processing, without returning any further response
In both cases, the method handleRequest()
above is processing the request, as its name implies. This request is an event implemented as a Map<String, String>
.
All right! Now our new Java project is generated and while the class AwsLambdaSimple
presented above (which will be deployed in fine as an AWS Lambda function) doesn't do much, it is sufficiently complete in order to demonstrate our use case. So let's deploy our cloud infrastructure. But first, we need to create an AWS S3 bucket in order to store in it our Lambda function. The simplest way to do that is shown below:
$ aws s3 mb s3://bucket-$$
make_bucket: bucket-18468
bucket-18468
. The AWS S3 buckets are constrained to have a unique name across regions. And since it's difficult to guarantee the uniqueness of a name, we use here the Linux $$
function which generates a random number.
sam deploy --s3-bucket bucket-18468 --stack-name simple-lambda-stack --capabilities CAPABILITY_IAM
Uploading to 44774b9ed09001e1bb31a3c5d11fa9bb 4031 / 4031 (100.00%)
Deploying with following values
===============================
Stack name : simple-lambda-stack
Region : eu-west-3
Confirm changeset : False
Disable rollback : False
Deployment s3 bucket : bucket-18468
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Signing Profiles : {}
Initiating deployment
=====================
Uploading to 3af7fb4a847b2fea07d606a80de2616f.template 555 / 555 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add AwsLambdaSimpleRole AWS::IAM::Role N/A
+ Add AwsLambdaSimple AWS::Lambda::Function N/A
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:eu-west-3:495913029085:changeSet/samcli-deploy1673620369/0495184e-58ca-409c-9554-ee60810fec08
2023-01-13 15:33:00 - Waiting for stack create/update to complete
CloudFormation events from stack operations (refresh every 0.5 seconds)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::IAM::Role AwsLambdaSimpleRole -
CREATE_IN_PROGRESS AWS::IAM::Role AwsLambdaSimpleRole Resource creation Initiated
CREATE_COMPLETE AWS::IAM::Role AwsLambdaSimpleRole -
CREATE_IN_PROGRESS AWS::Lambda::Function AwsLambdaSimple -
CREATE_IN_PROGRESS AWS::Lambda::Function AwsLambdaSimple Resource creation Initiated
CREATE_COMPLETE AWS::Lambda::Function AwsLambdaSimple -
CREATE_COMPLETE AWS::CloudFormation::Stack simple-lambda-stack -
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - simple-lambda-stack in eu-west-3
Our Java class has been successfully deployed as an AWS Lambda function. Let's test it using the two invocation methods presented above.
$ aws lambda invoke --function-name AwsLambdaSimpleFunction --payload $(echo "{\"Hello\":\"Dude\"}" | base64) outputfile.txt
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
$ cat outputfile.txt
"Hello->Dude"
The listing above demonstrates the synchronous or RequestResponse
invocation. We pass a JSON-formatted payload as the input event and, since its default format is Base64, we need to convert it first. Since the invocation is synchronous, the caller waits for the response which is captured in the file outputfile.txt
. The returned status code is HTTP 200, as expected, meaning that the request has been correctly processed. Let's see the asynchronous or Event
invocation.
$ aws lambda invoke --function-name AwsLambdaSimpleFunction --payload $(echo "{\"Hello\":\"Dude\"}" | base64) --invocation-type Event outputfile.txt
{
"StatusCode": 202
}
This time the --invocation-type
is Event
and, consequently, the returned status code is HTTP 202, meaning that the request has been accepted but not yet processed. The file output.txt
is empty, as there is no result.
This concludes our use case showing the AWS Lambda functions deployment in Java via the SAM tool. Don't forget to clean up your environment before leaving by running:
$ aws s3 rm --recursive s3://bucket-18468
$ aws s3 rb --force s3://bucket-18468
$ aws cloudformation delete-stack --stack-name simple-lambda-stack
Enjoy!
Opinions expressed by DZone contributors are their own.
Comments