Java with Lambda does not have to be slow

-

As a seasoned Java programmer, usually with Spring Boot, it hurts to learn that most lambdas use JavaScript/TypeScript. What if you want to keep writing your Lambda in Java? A query in google teaches us that Quarkus is the way to go. This blog post introduces Quarkus, GraalVM, and we top it off with AWS CDK to deploy the sample.

Introducing Quarkus

The goal for Quarkus is to create a framework that makes Java applications ready for the cloud. It uses GraalVM to build Docker images running Java as native applications. The improvements to first response times are impressive, as is the memory consumption. This procedure does limit the libraries that you can use. However, more and more extensions become available. I got inspired to look into Quarkus (again) after reading this very cool blog post about running Lucene as a Lambda.

The trick with Quarkus is creating a native image with as little as possible reflection and, as much as possible build metadata processing upfront. That way, less processing is required at the start time. Classes only required during setup can be removed. That way, the image and the used resources are lower. Read more about it on the Quarkus page Container First.

Generating the Lambda

If there is one thing you have to like about Quarkus besides blazing-fast java applications, it is their documentation and tutorials. The tutorial Quarkus – Amazon Lambda¬†gives you a jump start to create your own Lambda. Some aspects that I like is that you can include multiple Lambdas in one package. Use an environment variable to choose the Lambda to run. The generated code also comes with a few scripts that help you deploy and interact with your Lambda. I don’t use them; I prefer the CDK approach. Still, it is good to know they are available. The following code block shows the maven command to generate our project using the archetype provided by Quarkus.


mvn archetype:generate \
       -DarchetypeGroupId=io.quarkus \
       -DarchetypeArtifactId=quarkus-amazon-lambda-archetype \
       -DarchetypeVersion=1.12.2.Final \
       -DgroupId=eu.luminis.aws \
       -DartifactId=aws-quarkus-lambda

After an “mvn clean package”, the target folder is available with the mentioned shell scripts and a function.zip file. This function.zip file contains the optimized code for the Lambda. Later in this blog, we use this zip file in our CDK stack to upload the Lambda. We need to add one property to the maven config. For the native profile, we add the property “quarkus.native.container-build”. I removed the generated Lambdas and created my own SendMessageLambda and HelloLambda. If you have only one Lambda available, this is automatically selected. I like to experiment with two Lambdas in one distribution.

Using Java for a Lambda does require a specific interface. AWS does provide this. The Java class that receives the events had to implement the interface “RequestHandler”. You can specify the InputObject and OutputObject yourself. The following code block shows the java code for the Lambda-class.


@Named("message")
public class SendMessageLambda implements RequestHandler<InputObject, OutputObject> {
    @Inject
    ProcessingService service;

    @Override
    public OutputObject handleRequest(InputObject input, Context context) {
        String process = service.process(input.getGreeting(), input.getName());
        OutputObject out = new OutputObject();
        out.setResult(process);
        out.setRequestId(context.getAwsRequestId());

        return out;
    }
}

Notice the “@Named” annotation; you used this annotation to specify the Lambda currently in use. Use the application.properties file or a system environment parameter. The “@Inject” annotation configures the dependency injection.

Deploying the Lambda using CDK

With the Lambda available as a zip file in the maven target folder, we can use CDK to deploy the Lambda to AWS. Within the project, we create a CDK folder and initialize the CDK project using TypeScript.

$ cdk init app --language typescript

The configuration of the stack to include the Lambda looks like this.


const quarkusLambda = new lambda.Function(this, "QuarkusLambda", {
  runtime: lambda.Runtime.PROVIDED_AL2,
  handler: "io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest",
  code: lambda.Code.fromAsset('../target/function.zip'),
});

Do not change the handler; Quarkus provide this class. Notice the runtime that we use. As we create a native image, we use the provided runtime. Before we run cdk deploy, we first have to build the java project using maven and the native profile.

$ mvn clean package -Pnative
$ cd cdk
$ cdk deploy

Using the AWS console, we can test the Lambda. Open the Lambda, click on the test tab, and enter the following JSON document as input.


{
  "name": "Hello",
  "greeting": "readers"
}

After the invocation, you see the following output.

Screenshot of AWS Lambda Test

Notice the init duration (196.37 ms) and the duration (4.69 ms). On a second run, the init duration is gone, and the duration becomes 1 ms.

Use environment variables to select the right Lambda

Using the console, we provide the following environment variable “quarkus_lambda_handler”. Go to the “Configuration” tab, environment variables and add the one mentioned above. In our case, we give it the value “hello”, now the output becomes “Hello World!”, no matter the input. As we change the configuration, the Lambda requires a cold restart again.

Of course, we also want to be able to configure this parameter using CDK. This configuration is straightforward; add an environment block to the lambda specification.


const quarkusMessageLambda = new lambda.Function(this, "QuarkusMessageLambda", {
  runtime: lambda.Runtime.PROVIDED_AL2,
  handler: "io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest",
  code: lambda.Code.fromAsset('../target/function.zip'),
  environment: {
    quarkus_lambda_handler: "message"
  }
});

Concluding

Yes, we still can use our java knowledge to create high performing lambdas. Of course, we created a basic Lambda, but more advanced lambdas are possible as well. Quarkus is perfect for making these java Lambdas. I want to look at Quarkus as a modern rest endpoint combined with the AWS API Gateway in the next blog. So stay tuned for more Quarkus.

References