How to Write a Lambda Function with Java 8 - Sumo Logic

Done

How to Write AWS Lambda Function with Java 8

AWS Lambda allows a developer to create a function which can be uploaded and configured to execute in the AWS Cloud. This function can be written in any of a growing number of languages, and this post will specifically address how to create an AWS Lambda function with Java 8. We’ll go through the function itself, and then walk through the process of uploading and testing your function through the AWS Console.

What is an AWS Lambda Function?

AWS Lambda functions are event-driven components of functionality. These functions respond to events such as the passage of data from an SQS queue to a Lambda function, or a change in the state of a file within S3. The event is passed into the function as the first parameter. Lambda functions themselves are completely stateless, which means that you have no guarantee where a function will be executed, nor any notion of how many times the function has been executed on the particular server, if at all.

AWS Lambda Example: A Simple Zipcode Validator

The Internet is rife with “Hello, World!” examples, which generally do a less-than-OK job of explaining the basics of how a language works, and provide little in the way of solving actual problems. For this example, we’re going to create a simple zipcode validator that responds to a new address being added to a DynamoDB table. Admittedly, this is definitely not the smartest use for a Lambda function, but it should serve to demonstrate how to wire a workable solution together.

Step 0: Before You Start.

Creating and deploying this example shouldn’t cost you much if anything from within the free tier, however, please ensure that you disable any triggers when you’re done and scale back the provisioning or delete the DynamoDB table. You are responsible for understanding what charges you may incur by using AWS. This article is merely a demonstration of potential uses.

Step 1. Create a DynamoDB table.

You’ll need to create a new table and enable a stream on it. To do this, navigate to the DynamoDB console and click on Create table. Choose a name for your table and set the Primary Key. For this example, I set up a table called US_Address_Table with a primary key of id and type String, and no sort key. Ensure Use default setting is selected, and then click on Create.

Ensure that your table is selected, and then, from the Overview tab, click on Manage Stream. Select New image from the view type, and then click on Enable.

Before you leave this page, look for the Amazon Resource Name (ARN) under Table Details. It should look similar to this: arn:aws:dynamodb:us-west-2:321593873910:table/US_Address_Table Copy it to your clipboard or a text document, as you’ll need it for the next step.

Step 2. Create a Role your Lambda function.

The function will need permissions to execute itself; then, it will also need permissions to update the DynamoDB table. To do this, navigate to the Identity and Access Management console and click on Roles. Click the Create New Role button, and choose a name for your role. (For this example, I called my lambda-validator.) Click Next Step.

Click on the Select button next to AWS Lambda. Use the filter to find the AWSLambdaDynamoDBExecutionRole. Check the box next to the role, and then click Next Step. On the next page you’ll be shown the role details. Click on Create Role. You now have a role that will allow your function to run and access DynamoDB streams.

Click on the name of your role to edit it. Click on the Inline Policies section at the bottom of the page. Since we don’t have any policies yet, this should be empty. You should see something similar to “There are no inline policies to show. To create one, click here.” Click the link. Click Select under Policy Generator. Enter the following information:
Effect: Allow
AWS Service: Amazon DynamoDB
Actions: Update Item
Amazon Resource Name (This is the ARN for your DynamoDB table that we copied a couple of paragraphs ago).

Click Add Statement, and then Next Step at the bottom of the page. You can choose a new name for your policy at this point if you would like. I’m going to change mine to lambda-validator-update-dynamodb.

Click Apply Policy.

That’s enough configuration for now. Let’s move on to the fun stuff.

Step 4. Let’s Code the Lambda Function!

We’re going to have a couple of dependencies for this function, so start by setting up a project which includes the following libraries. At the time of writing, I used version 1.1.0 for each.

aws-lambda-java-core
aws-lambda-java-events

Next, you’ll need an Address object. I created the one below, and then used IntelliJ to automatically create accessor functions, which are excluded for brevity.
package com.example;

public class Address {
private String city;
private String state;
private String zipcode;
private Boolean validated;
}

/*And now, the Lambda function itself. Then, I’ll go over each part in detail.*/
package com.example;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.UpdateItemSpec;
import com.amazonaws.services.dynamodbv2.document.utils.ValueMap;
import com.amazonaws.services.dynamodbv2.model.ReturnValue;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.DynamodbEvent;
import com.amazonaws.services.lambda.runtime.events.DynamodbEvent.DynamodbStreamRecord;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.regex.Pattern;

public class AddressValidator implements RequestHandler<DynamodbEvent, String> {
private static final String ID = "id";
private static final String INSERT = "INSERT";
private static final String TABLE_NAME = "US_Address_Table";
private static final String ZIP_CODE_REGEX = "^[0-9]{5}(?:-[0-9]{4})?\$";

private DynamoDB dynamoDB;
private Table table;

public String handleRequest(DynamodbEvent ddbEvent, final Context context) {
LambdaLogger logger = context.getLogger();
Pattern pattern = Pattern.compile(ZIP_CODE_REGEX);
ObjectMapper objectMapper = new ObjectMapper();
for (DynamodbStreamRecord record : ddbEvent.getRecords()){
try {
if (record.getEventName().equals(INSERT)) {
if (null == dynamoDB) {
dynamoDB = new DynamoDB(new AmazonDynamoDBClient().withRegion(Regions.fromName(record.getAwsRegion())));
table = dynamoDB.getTable(TABLE_NAME);
}
UpdateItemSpec updateItemSpec = new UpdateItemSpec()
.withPrimaryKey(ID, record.getDynamodb().getKeys().get(ID).getS())
.withReturnValues(ReturnValue.UPDATED_NEW);
table.updateItem(updateItemSpec);
}
}
} catch (IOException e) {
logger.log("Exception thrown when validating Zip Code. " + e.getMessage());
}
}
return "Validated " + ddbEvent.getRecords().size() + " records.";
}
}

If we take a quick walk through that code, there are a couple of things to note. The name of the main function, and the extension of RequestHandler are integral to this function being imported into AWS as a Lambda function.

At first, I initialized the AmazonDynamoDBClient and Table right upfront. I discovered that if you don’t specify a region, the code will automatically assign a region at run time, which may or may not match where your DynamoDB table exists. And we won’t know where the data is coming from until the function is invoked. (This allowed me to instantiate these objects only if a validation was required.)

The DynamodbEvent actually includes a list of DynamodbStreamRecords. These records may be updates, inserts, or deletes. Because of my admittedly poor decision to simply update the record in the same table, I ran into a problem where the same record was being sent multiple times to be validated. Given that Lambdas are charged per execution, this could get expensive fairly quickly. In hindsight, it would have made more sense to write invalid records to a different table, or submit to a SQS queue for further processing elsewhere. (Anyway, I only run the validation for inserted records, ignoring all others.)

So we have a function, and now we’re ready to upload it to AWS. With a function written in Java 8, you have the option to upload it as a zip file, or as a fat jar. At the time of writing, there do appear to be some complications deploying the function from within a jar, so I would recommend a zip file if you have the option. The zip file should contain all the class files for your project, as well as any included libraries.

Let’s head to the Lambda console. From here you can create your function and configure it. Click on Create a Lambda function. At this point, you’ll be presented a selection of blueprints. At the time of writing, only Python and Node.js were represented in the samples. Click Skip, and then on the triggers page, click Next. We’ll configure the triggers when we’ve tested the function.

Choose a name and description for your Lambda, and select Java 8 as the runtime environment. I simply named mine AddressValidator and left the description blank. Go down to the next section, and click Upload. Select your zip folder containing your function.

For the handler, you want the full name of your function. If you copied my example exactly, that would be com.example.AddressValidator. For the role, select Choose an existing role, and then select the role we created back at the beginning. (Mine was called lambda-validator.)

Under the advanced settings, you can select Memory and Timeout for the function. I found the function used a maximum of about 80MB of memory, but if I configured it with less than 256MB, then it exceeded the 15-second timeout I had configured. I set mine up to use 512MB of memory with a 15-second timeout. Billing is calculated based on requests x memory usage x time for execution. See Optimizing AWS Lambda Cost and Performance Through Monitoring for more information. Ultimately, you’ll want to experiment to find your optimal combination.

Finally, I left the VPC field set to NoVPC, and then clicked on Next. The function will now be uploaded and converted into a Lambda.

Step 6. Test Your Function Synchronously Within the AWS Console

To test the function, click Actions, and then select configure test event. Here you can use an existing event template, but you’ll need to tweak it to match your schema. Alternatively, you can simply use the json below if you’re working in US-WEST-2.

 {   "Records": [     {       "eventID": "1",       "eventVersion": "1.0",       "dynamodb": {         "Keys": {           "id": {             "S": "111-222-333"           }         },         "NewImage": {           "address": {             "S": "{\"address1\":\"123 Main St\",\"city\":\"Portland\",\"state\":\"OR\",\"zipcode\":\"97229\"}"           },           "id": {             "S": "111-111-111"           }         },         "StreamViewType": "NEW_IMAGES",         "SequenceNumber": "111-222-333",         "SizeBytes": 26       },       "awsRegion": "us-west-2",       "eventName": "INSERT",       "eventSourceARN": "arn:aws:dynamodb:us-west-2:account-id:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899",       "eventSource": "aws:dynamodb"     }   ] }

Click on Save and Test, and your function should execute. You may notice that a new record will show up in your database table at this point as well.

If you run into Null Pointer Exceptions, check your code and your test code to ensure that your field names are properly cased. If you have permission errors, you’ll want to look at the ARN for the table, and check your roles again. Finally, adding some logger.log statements like logger.log(“Creating the updateItemSpec”) to the function will prove invaluable in trying to find bugs.

In any case, updating the function is as easy as clicking the code tab, and uploading a new zip file.

Step 7. Trigger the Lambda Function from DynamoDB

When you have it running perfectly, you can then enable a trigger on your DynamoDB table. Go back to the DynamoDB console and select your table. Click on the Triggers tab. Click on Create trigger, and then choose Existing Lambda function. Choose the Validation function from the drop-down, select a batch size, and check Enable trigger. For my example I chose a batch size of 1, since I was planning on entering addresses into the table manually.

Finally, click on Create, and you should be good to go. Manually enter an address record, and within a couple of seconds, you should see if updated to include the validated property in the address. To see it performing, you can also get into Cloudwatch and look for the logs.

Request A Free Sumo Logic Demo

Fill out the form below and a Sumo Logic representative will contact you to schedule your free demo.
“Sumo Logic brings everything together into one interface where we can quickly scan across 1,000 servers and gigabytes of logs and quickly identify problems. It’s awesome software and awesome support.”

Jon Dokuli,
VP of Engineering

Thank you for signing up for Sumo Logic.

We are creating your account now.