Working with layers for Java Lambda functions - AWS Lambda

Working with layers for Java Lambda functions

A Lambda layer is a .zip file archive that contains supplementary code or data. Layers usually contain library dependencies, a custom runtime, or configuration files. Creating a layer involves three general steps:

  1. Package your layer content. This means creating a .zip file archive that contains the dependencies you want to use in your functions.

  2. Create the layer in Lambda.

  3. Add the layer to your functions.

This topic contains steps and guidance on how to properly package and create a Java Lambda layer with external library dependencies.

Prerequisites

To follow the steps in this section, you must have the following:

Note

Ensure that the Java version that Maven refers to is the same as the Java version of the function that you intend to deploy. For example, for a Java 21 function, the mvn -v command should list Java version 21 in the output:

Apache Maven 3.8.6 ... Java version: 21.0.2, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk-21.jdk/Contents/Home ...

Throughout this topic, we reference the layer-java sample application on the awsdocs GitHub repository. This application contains scripts that download the dependencies and generate the layer. The application also contains a corresponding function that uses dependencies from the layer. After creating a layer, you can deploy and invoke the corresponding function to verify that everything works properly. Because you use the Java 21 runtime for the functions, the layers must also be compatible with Java 21.

The layer-java sample application contains a single example within two sub-directories. The layer directory contains a pom.xml file that defines the layer dependencies, as well as scripts to generate the layer. The function directory contains a sample function to help test that the layer works. This tutorial walks through how to create and package this layer.

Java layer compatibility with Amazon Linux

The first step to creating a layer is to bundle all of your layer content into a .zip file archive. Because Lambda functions run on Amazon Linux, your layer content must be able to compile and build in a Linux environment.

Java code is designed to be platform-independent, so you can package your layers on your local machine even if it doesn't use a Linux environment. After you upload the Java layer to Lambda, it'll still be compatible with Amazon Linux.

Layer paths for Java runtimes

When you add a layer to a function, Lambda loads the layer content into the /opt directory of that execution environment. For each Lambda runtime, the PATH variable already includes specific folder paths within the /opt directory. To ensure that the PATH variable picks up your layer content, your layer .zip file should have its dependencies in the following folder paths:

  • java/lib

For example, the resulting layer .zip file that you create in this tutorial has the following directory structure:

layer_content.zip └ java └ lib └ layer-java-layer-1.0-SNAPSHOT.jar

The layer-java-layer-1.0-SNAPSHOT.jar JAR file (an uber-jar that contains all of our required dependencies) is correctly located in the java/lib directory. This ensures that Lambda can locate the library during function invocations.

Packaging the layer content

In this example, you package the following two Java libraries into a single JAR file:

  • aws-lambda-java-core – A minimal set of interface definitions for working with Java in AWS Lambda

  • Jackson – A popular suite of data-processing tools, particularly for working with JSON.

Complete the following steps to install and package the layer content.

To install and package your layer content
  1. Clone the aws-lambda-developer-guide GitHub repo, which contains the sample code that you need in the sample-apps/layer-java directory.

    git clone https://github.com/awsdocs/aws-lambda-developer-guide.git
  2. Navigate to the layer directory of the layer-java sample app. This directory contains the scripts that you use to create and package the layer properly.

    cd aws-lambda-developer-guide/sample-apps/layer-java/layer
  3. Examine the pom.xml file. In the <dependencies> section, you define the dependencies that you want to include in the layer, namely the aws-lambda-java-core and jackson-databind libraries. You can update this file to include any dependencies that you want to include in your own layer.

    Example pom.xml
    <dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.17.0</version> </dependency> </dependencies>
    Note

    The <build> section of this pom.xml file contains two plugins. The maven-compiler-plugin compiles the source code. The maven-shade-plugin packages your artifacts into a single uber-jar.

  4. Ensure that you have permissions to run both scripts.

    chmod 744 1-install.sh && chmod 744 2-package.sh
  5. Run the 1-install.sh script using the following command:

    ./1-install.sh

    This script runs mvn clean install in the current directory. This creates the uber-jar with all required dependencies in the target/ directory.

    Example 1-install.sh
    mvn clean install
  6. Run the 2-package.sh script using the following command:

    ./2-package.sh

    This script creates the java/lib directory structure that you need to properly package your layer content. It then copies the uber-jar from the /target directory into the newly created java/lib directory. Finally, the script zips the contents of the java directory into a file named layer_content.zip. This is the .zip file for your layer. You can unzip the file and verify that it contains the correct file structure, as shown in the Layer paths for Java runtimes section.

    Example 2-package.sh
    mkdir java mkdir java/lib cp -r target/layer-java-layer-1.0-SNAPSHOT.jar java/lib/ zip -r layer_content.zip java

Creating the layer

In this section, you take the layer_content.zip file that you generated in the previous section and upload it as a Lambda layer. You can upload a layer using the AWS Management Console or the Lambda API via the AWS Command Line Interface (AWS CLI). When you upload your layer .zip file, in the following PublishLayerVersion AWS CLI command, specify java21 as the compatible runtime and arm64 as the compatible architecture.

aws lambda publish-layer-version --layer-name java-jackson-layer \ --zip-file fileb://layer_content.zip \ --compatible-runtimes java21 \ --compatible-architectures "arm64"

From the response, note the LayerVersionArn, which looks like arn:aws:lambda:us-east-1:123456789012:layer:java-jackson-layer:1. You'll need this Amazon Resource Name (ARN) in the next step of this tutorial, when you add the layer to your function.

Adding the layer to your function

In this section, you deploy a sample Lambda function that uses the Jackson library in its function code, then you attach the layer. To deploy the function, you need a Lambda execution role. If you don't have an existing execution role, follow the steps in the collapsible section. Otherwise, skip to the next section to deploy the function.

To create an execution role
  1. Open the roles page in the IAM console.

  2. Choose Create role.

  3. Create a role with the following properties.

    • Trusted entityLambda.

    • PermissionsAWSLambdaBasicExecutionRole.

    • Role namelambda-role.

    The AWSLambdaBasicExecutionRole policy has the permissions that the function needs to write logs to CloudWatch Logs.

To deploy the Lambda function
  1. Navigate to the function/ directory. If you're currently in the layer/ directory, then run the following command:

    cd ../function
  2. Review the function code. The function takes in a Map<String, String> as input, and uses Jackson to write the input as a JSON String before converting it into a pre-defined F1Car Java object. Finally, the function uses fields from the F1Car object to construct a String that the function returns.

    package example; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.Map; public class Handler { public String handleRequest(Map<String, String> input, Context context) throws IOException { // Parse the input JSON ObjectMapper objectMapper = new ObjectMapper(); F1Car f1Car = objectMapper.readValue(objectMapper.writeValueAsString(input), F1Car.class); StringBuilder finalString = new StringBuilder(); finalString.append(f1Car.getDriver()); finalString.append(" is a driver for team "); finalString.append(f1Car.getTeam()); return finalString.toString(); } }
  3. Build the project using the following Maven command:

    mvn package

    This command produces a JAR file in the target/ directory named layer-java-function-1.0-SNAPSHOT.jar.

  4. Deploy the function. In the following AWS CLI command, replace the --role parameter with your execution role ARN:

    aws lambda create-function --function-name java_function_with_layer \ --runtime java21 \ --architectures "arm64" \ --handler example.Handler::handleRequest \ --timeout 30 \ --role arn:aws:iam::123456789012:role/lambda-role \ --zip-file fileb://target/layer-java-function-1.0-SNAPSHOT.jar

At this point, you can optionally try to invoke your function before attaching the layer. If you try this, then you should get a ClassNotFoundException because your function cannot reference the requests package. To invoke your function, use the following AWS CLI command:

aws lambda invoke --function-name java_function_with_layer \ --cli-binary-format raw-in-base64-out \ --payload '{ "driver": "Max Verstappen", "team": "Red Bull" }' response.json

You should see output that looks like this:

{ "StatusCode": 200, "FunctionError": "Unhandled", "ExecutedVersion": "$LATEST" }

To view the specific error, open the output response.json file. You should see a ClassNotFoundException with the following error message:

"errorMessage":"com.fasterxml.jackson.databind.ObjectMapper","errorType":"java.lang.ClassNotFoundException"

Next, attach the layer to your function. In the following AWS CLI command, replace the --layers parameter with the layer version ARN that you noted earlier:

aws lambda update-function-configuration --function-name java_function_with_layer \ --cli-binary-format raw-in-base64-out \ --layers "arn:aws:lambda:us-east-1:123456789012:layer:java-jackson-layer:1"

Finally, try to invoke your function using the following AWS CLI command:

aws lambda invoke --function-name java_function_with_layer \ --cli-binary-format raw-in-base64-out \ --payload '{ "driver": "Max Verstappen", "team": "Red Bull" }' response.json

You should see output that looks like this:

{ "StatusCode": 200, "ExecutedVersion": "$LATEST" }

This indicates that the function was able to use the Jackson dependency to properly execute the function. You can check that the output response.json file contains the correct returned String:

"Max Verstappen is a driver for team Red Bull"

You can now delete the resources that you created for this tutorial, unless you want to retain them. By deleting AWS resources that you're no longer using, you prevent unnecessary charges to your AWS account.

To delete the Lambda layer
  1. Open the Layers page of the Lambda console.

  2. Select the layer that you created.

  3. Choose Delete, then choose Delete again.

To delete the Lambda function
  1. Open the Functions page of the Lambda console.

  2. Select the function that you created.

  3. Choose Actions, Delete.

  4. Type delete in the text input field and choose Delete.