Deploying Java Serverless Functions as AWS Lambda

The 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:

Shell
 
$ 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:

Shell
 
$ 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]:

The command 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
As soon as the command terminates, you may open the new project in your preferred IDE and inspect the generated code. Once finished, you may proceed with a first build, as follows:

Shell
 
$ 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:

YAML
 
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:

Java
 
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:

Shell
 
$ aws s3 mb s3://bucket-$$
make_bucket: bucket-18468

Here we just created an S3 bucket having the name of 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. 
Shell
 
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.

Shell
 
$ 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.

Shell
 
$ 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:

Shell
 
$ aws s3 rm --recursive s3://bucket-18468
$ aws s3 rb --force s3://bucket-18468
$ aws cloudformation delete-stack --stack-name simple-lambda-stack

Enjoy!

 

 

 

 

Top