AWS Open Source Blog

Packaging and deploying AWS Lambda functions written in Java with AWS Cloud Development Kit

Many Java applications use Apache Maven or Gradle for building and managing the project. These tools help map how to build a particular piece of software, along with its different dependencies. In almost every scenario, these applications will depend on several external dependencies/libraries. AWS Lambda functions written in Java also use these tools for packaging software.

Previously, if you wanted to build and package Lambda functions with external dependencies in AWS Cloud Development Kit (AWS CDK) using Java or other supported language runtimes, you needed to create artifacts that included all the required dependencies. You would typically do this manually or via wrapper scripts. You could use AWS Serverless Application Model (AWS SAM), which does this out of the box; but, thanks to AWS CDK Assets (specifically, the aws-s3-assets module), this is now possible with AWS CDK, too.

Lambda functions written with the Python or Node.js runtime can leverage higher level constructs building on top of aws-s3-assets. There are no higher level constructs for packaging Lambda functions with external dependencies written in other languages such as Java yet, but the aws-s3-assets module can be used regardless.

In this article, we’ll show how to build and package Lambda functions written in Java with external dependencies via AWS CDK. You can download the code used in this walkthrough from the project’s GitHub repo.

Overview

In this example, we set up a basic serverless architecture using AWS CDK. It creates a REST API using Amazon API Gateway and Lambda proxy integration. The CDK application will create two Lambda functions written in Java and maintained as separate Maven projects.Project structure for the demo CDK application

├── infrastructure          # Infrastructure code vi CDK (Java).
├── software                # Holds business logic in AWS lambda functions
│   ├── FunctionOne         # Sample Lambda function (Maven project)  
│   └── FunctionTwo         # Sample Lambda function (Maven project)
└── ...

Deploying the solution

To deploy the application, the solution uses AWS CDK. As a prerequisite, you must have AWS CDK, Java 11, and Docker installed. You must also have the AWS Command Line Interface (AWS CLI) configured.

Step 1: Clone the GitHub repository and synthesize the application with the AWS CDK. Run the following commands in a terminal:

git clone git@github.com:aws-samples/cdk-lambda-packaging-java.git
cd cdk-lambda-packaging-java/infrastructure/
cdk synth

Note: When running the synth command for the first time, the CDK application will pull the Docker image for Java 11. This step might take few minutes to complete depending on your internet connection bandwidth.

Step 2: Deploy the application:

cdk deploy

Approve the AWS Identity and Access Management (IAM)-related changes and let the stack deploy. After deployment, the stack outputs the API Gateway HTTP API endpoint.

Step 3: Validate the deployed application by copying the HttpApi URL from the output in Step 2:

curl https://<hash>.execute-api.<region>.amazonaws.com/one

{ "message": "hello world", "location": "<some IP>" }

curl https://<hash>.execute-api.<region>.amazonaws.com/two

{ "message": "hello world", "location": "<some IP>" }

We deployed two Lambda functions with external dependencies, built and packaged natively via AWS CDK.

Walkthrough of the AWS CDK application

Now that we have seen how to build and deploy the demo application, let’s dive into the code for the CDK stack, which takes care of packaging and building the two Lambda functions out of the box.

The InfrastructureStack.java file in the infrastructure folder sets up the architecture shown above.

Setting up the Lambda functions

In this example, we have created a Java 11 Lambda function. The rest of the configuration is fairly typical, except for how the code for the function is configured. It uses the Code.fromAsset API to pass the path to the directory where the code for the Lambda function exists along with bundling options:

Function functionOne = new Function(this, "FunctionOne", FunctionProps.builder()
      .runtime(Runtime.JAVA_11)
      .code(Code.fromAsset("../software/", AssetOptions.builder()
                .bundling(builderOptions
                        .build())
                .build()))
        .handler("helloworld.App")
        .memorySize(1024)
        .timeout(Duration.seconds(10))
        .logRetention(RetentionDays.ONE_WEEK)
        .build());

Bundling options and commands

When defining an asset via Code.fromAsset API, the bundling options command is used to specify a command to run inside a Docker container. The command can read the contents of the asset source from /asset-input and writes files under /asset-output (directories mapped inside the container). The files under /asset-output will be zipped and uploaded to Amazon Simple Storage Service (Amazon S3) as the asset by default. In our example, the asset is already zipped in form of .jar, so we have set the output type as ARCHIVED explicitly.

We are using the official Docker image for Java 11. The packaging instructions for function depends upon how you want to build the application. In this example, because we’re using Maven, the packaging instructions follow that setup.

List<String> functionOnePackagingInstructions = Arrays.asList(
        "/bin/sh",        
        "-c",       
        "cd FunctionOne " +
        "&& mvn clean install " +
        "&& cp /asset-input/FunctionOne/target/functionone.jar /asset-output/");        

BundlingOptions.Builder builderOptions = BundlingOptions.builder()
        .command(functionOnePackagingInstructions)
        .image(Runtime.JAVA_11.getBundlingImage())
        .user("root");
        .outputType(BundlingOutput.ARCHIVED)

We have optionally configured it to mount the local volumes. This can be useful in cases where you would want to mount your cache directory to avoid downloading all the external dependencies on each build. In the example below, we have mounted to local .m2 directory to avoid downloading external dependencies again.

BundlingOptions.Builder builderOptions = BundlingOptions.builder()
                .command(functionOnePackagingInstructions)
                .image(Runtime.JAVA_11.getBundlingImage())
                .volumes(singletonList( DockerVolume.builder() .hostPath(System.getProperty("user.home") + "/.m2/") .containerPath("/root/.m2/") .build() ))
                .user("root")

With this setup, the CDK application takes care of building and packaging the Lambda function during the synthesizing or deploy stage. We no longer need a wrapper script that makes sure to build the Lambda function artifact first.

Cleanup

Run the following command to delete the resources created while following this walkthrough:

cdk destroy LambdaPackagingStack

Conclusion

AWS CDK lets you apply existing skills and tools to the task of building cloud infrastructure. The launch of a higher level construct in the form of the AWS CDK Assets module allows developers to deploy CDK apps that include constructs with assets. A common example of an asset is a directory that contains the handler code for a Lambda function along with external dependencies.

This article shows how to build and package AWS Lambda functions written in Java with external dependencies using AWS CDK natively. The approach can be applied to other AWS Lambda-supported language runtimes as well.

To learn more, visit the AWS CDK Assets module page and the AWS CDK product page.

Pankaj Agrawal

Pankaj Agrawal

Pankaj is a Solutions Architect at AWS, where he works with different customers across Nordics, helping them to make best use of AWS services and accelerate their journey to cloud. In his spare time, he enjoys running into the woods and loves to learn new technical skills. He is quite passionate about DevOps and Serverless technologies.