Deploying Java Lambdas using AWS CDK and Maven

Summary

Java is one of the most popular language in use today. However, I have not run across a lot of people using Java with AWS Lambda or CDK. In this post, I will demonstrate how to deploy Java to AWS Lambda using AWS CDK and provide a sample repo structure to copy for your own purposes.

Java

Java has a long history of running high performant applications for massive customers. Java and the JVM continue to be very popular today. AWS has made significant investments into the Java ecosystem. This includes maintaining their own OpenJDK distribution, Corretto. This is due to Java’s popularity, particularly in large companies. So it’s no surprise that AWS would want to support these customers.

AWS Lambda currently supports the following Java versions:

  • Java 11
  • Java 8 Corretto
  • Java 8 OpenJDK

For this post, we will be using the Java 8 OpenJDK runtime.

Bundling Java for AWS Lambda

Much like other languages supported by Lambda, your Java applications can be bundled in a .jar or zip file to be deployed to AWS Lambda. The handler can be any class implementing the RequestHandler interface. This means we are not limited to Java the language on AWS Lambda. We can, in fact, use any language capable of compiling for the JVM, including Kotlin.

The com.amazonaws.services.lambda.runtime.RequestHandler interface is included in the Maven dependency:

  <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-lambda-java-core</artifactId>
      <version>${lambda.version}</version>
  </dependency>

The full class and method name will be designated as the handler for AWS Lambda. When lambda executes, it will call the method designated, passing in the event that was used to invoke the lambda.

Using Lambda Layers

Layers are a feature of AWS lambda that allows multiple lambda functions to share assets. Using layers we can bundle all the dependencies of an application into a single .zip file and any lambda’s can use it. This means they do not need to contain all the external dependencies, keeping the deployment package small.

In the sample repository, we have a submodule, layer that is responsible for bundling the dependencies for layer creation. The layer is created in CDK using:

// Create a layer from the layer module
final LayerVersion layer = new LayerVersion(this, "layer", LayerVersionProps.builder()
        .code(Code.fromAsset("../layer/target/bundle"))
        .compatibleRuntimes(Arrays.asList(Runtime.JAVA_8))
        .build()
);

The maven assembly plugin bundles all the dependencies into a single jar and outputs to a directory. For AWS Lambda, java dependencies must be in the java/lib directory of a layer. The code above will create a .zip file and upload it to S3. Then, we specify the layer in the lambda function.

Using the repository

The structure of the sample repository includes a parent pom.xml where we can add any dependencies and setup the base project structure. Personally, I tend to ignore the Maven convention of holding sources under src/main/java and opt for a flat repository structure where source code is simply put under src. This is defined in the parent pom.

In addition, the repository has three maven modules:

  • infra - Contains CDK implementation for deploying AWS resources
  • lambdas - Contains the lambdas to be deployed
  • layer - Creates the layer bundle

In order to work with CDK in Java, you will need to install CDK. This requires NodeJS, which can be install with nvm.

In the sample repository, the code for deploying the lambdas resides in cdk.Stack. This class, creates the lambda and nothing else. However, if you have other AWS resources that you would like to deploy, they can be place in here.

// Example of creating a lambda in CDK java
new Function(this, "JavaFn", FunctionProps.builder()
        .runtime(Runtime.JAVA_8)
        .code(Code.fromAsset("../lambdas/target/lambdas.jar"))
        .handler("lambdas.ExampleLambda")
        .layers(Arrays.asList(layer))
        .memorySize(1024)
        .timeout(Duration.seconds(30))
        .logRetention(RetentionDays.ONE_WEEK)
        .build());

Now, venturing into the lambdas module, we see a single ExampleLambda class. This class implements the RequestHandler interface. If you wanted to deploy more than one lambda, you could simply define and additional class and add the lambda as above.

For the purposes of this sample, the implementation is pretty simple:

package lambdas;

import java.util.Map;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.google.common.base.Joiner;

public class ExampleLambda implements RequestHandler<Map<String, String>, String> {

    @Override
    public String handleRequest(final Map<String, String> event, final Context context) {
        System.out.println("Received event: " + event);
        // using an external dependency provided by layer
        final String msg = Joiner.on(" ").join("Hello", "from", "Java!");
        return null;
    }
}

Deployment

To deploy the sample, you should be logged into your AWS account utilizing the AWS CLI. There is a Makefile present in the root of the repository. This file will run the relevant commands:

make deploy

This target will build the submodules and deploy the lambda using CDK. The sample repo is located in Github. You can use it to get started deploying your own lambdas using Java.

Avatar
Kerry Wilson
AWS Certified IQ Expert | Cloud Architect

Coming from a development background, Kerry’s focus is on application development, infrastructure and security automation, and applying agile software development practices to IT operations in the cloud.

Related