AWS Lambda has been used by many developers for building and deploying serverless architecture or event-driven architecture applications. AWS Lambda functions can be developed using many languages like Go, Python, Java, NodeJS, or more.
In this article, we will be going through the Java option of AWS Lambda. We will see how a function can be developed using Java and what Lambda features can be enabled with it.
To build a Lambda function with Java, you need to first write code using the handler methods supported by the AWS Java SDK. Then, you need to build the function using build tools like Maven and Gradle. Enable the logging, tracing, do error handling, and finally, deploy the code to the Lambda service. Let’s go through each of these steps in detail.
In this article
Similar to any other Java application, a Lambda Java function also needs to be built and deployed. To build the Java function, we can use Maven or Gradle. A Deployment package for Java can be a zip file or a jar file. We can deploy functions using the AWS CLI, AWS Console, or the AWS Serverless Application Model (SAM).
AWS Lambda provides a few core libraries that are required for building a Java function:
There are a few other libraries you would need, for example:
You will also need a Maven or Gradle plugin to compile and build the code. Below is an example for Maven:
As mentioned above, we would need to generate the zip or jar file using a Maven plugin or Gradle task. Here’s an example of how to do it in Gradle:
#gradle task task buildZipPackage(type: Zip) { from compileJava from processResources into('lib') { from configurations.runtimeClasspath } dirMode = 0755 fileMode = 0755 }
This build configuration produces a deployment package in the build/distributions folder. The compileJava task compiles the function’s classes. The processResources task copies libraries from the build’s classpath into a folder named lib.
For maven, we would need to use the maven-shade-plugin:
#pom.xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.2</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin>
If the size of the deployment package (zip or jar) is less than 50 MB, you can directly upload the package to Lambda using AWS CLI, Console, or API. If the size is more, it has to be uploaded to the S3 bucket first and then point the Lambda to that upload.
The best way to deploy a package to Lambda is through AWS SAM or Serverless Framework. AWS SAM is supported by AWS and the serverless framework is widely used open-source software.
AWS SAM is an extension of AWS CloudFormation that provides a simplified syntax for defining serverless applications. The following example template defines a function with a deployment package in the build/distributions directory:
#template.yaml AWSTemplateFormatVersion: '2010-09-09' Transform: 'AWS::Serverless-2016-10-31' Description: An AWS Lambda application that calls the Lambda API. Resources: function: Type: AWS::Serverless::Function Properties: CodeUri: build/distributions/java-basic.zip Handler: example.Handler Runtime: java8 Description: Java function MemorySize: 1024 Timeout: 5 # Function's execution role Policies: - AWSLambdaBasicExecutionRole - AWSLambdaReadOnlyAccess - AWSXrayWriteOnlyAccess Tracing: Active
Using the sam build and sam deploy commands, we can deploy the function code to the Lambda service.
The sam build command processes this SAM template file, application code, and any applicable language-specific files and dependencies. Dependencies are specified in a manifest file that is included in the application, such as package.json for Node.js functions. It copies your application source code to folders under .aws-sam/build to be zipped and uploaded to Lambda.
The sam deploy command deploys the code to the AWS cloud. It creates the S3 bucket for the deployment package. It takes the deployment artifacts built with the sam build command, packages it, and uploads it to the S3 bucket created.
A Lambda function’s structure is very simple. We just need to implement a handler method that will be triggered whenever a function is invoked. However, you may need to import several other libraries, define global variables, connection URLs, etc.. to be used by the handler method.
The Handler method in function code processes events. When a function is invoked, Lambda executes the handler method. Once the processing is completed, it returns a response and becomes available to handle another event.
Here’s an example of a handler method:
// Handler value: example.Handler public class Handler implements RequestHandler<Map<String,String>, String>{ Gson gson = new GsonBuilder().setPrettyPrinting().create(); @Override public String handleRequest(Map<String,String> event, Context context) { LambdaLogger logger = context.getLogger(); String response = new String("200 OK"); // log execution details logger.log("ENVIRONMENT VARIABLES: " + gson.toJson(System.getenv())); logger.log("CONTEXT: " + gson.toJson(context)); // process event logger.log("EVENT: " + gson.toJson(event)); logger.log("EVENT TYPE: " + event.getClass().toString()); return response; } }
It accepts two inputs: event and context.
An Event can be of any type. The developer needs to define the type of object that event maps to in the handler method’s signature. In the above example, Java deserializes the event into a type that implements the Map<String,String> interface. So It can accept input like this:
{ "customerId": 1081, "firstName": “Matt”, "lastName": “Robins”, "status": “Active” }
Context provides methods that provide information about the function invocation, metadata, and execution environment. For example, what is the function name, function version, and time remaining for a timeout.
The aws-lambda-java-core library defines two interfaces for handler methods. These interfaces simplify handle configuration and validate the handler method signature at compile time.
By default, Lambda functions come with CloudWatch Logs for logging, which creates logGroup and logStreams for a function. For logging, we can use any library or module that supports stdout and stderr.
The aws-lambda-java-core library provides a logger class named LambdaLogger that can be accessed from the context object. LambdaLogger supports multiline logs as well.
Here’s an example:
@Override public String handleRequest(Object event, Context context) { LambdaLogger logger = context.getLogger(); String response = new String("Logging Successful"); // log execution details logger.log("ENV VARIABLES: " + gson.toJson(System.getenv())); logger.log("CONTEXT: " + gson.toJson(context)); // process event logger.log("EVENT: " + gson.toJson(event)); return response; }
You can view the logs either through the AWS Console or the CLI.
Lambda functions only support throwing runtime exceptions. They don’t allow checked exceptions. The function will throw the error back to the invoker with a specific message format that contains an error message, error type, and stack trace:
{ "errorMessage":"Input must contain alphanumeric value.", "errorType":"java.lang.InputTypeException", "stackTrace": [ "example.HandlerDisplay.handleRequest(HandlerDisplay.java:21)", "example.HandlerDisplay.handleRequest(HandlerDisplay.java:12)" ] }
Following is an example of Java code showing how to do error handling:
@Override public Integer handleRequest(List<Integer> event, Context context) { LambdaLogger logger = context.getLogger(); // process event Pattern p = Pattern.compile("^[a-zA-Z0-9]*$"); if ( ! p.matcher(event.get(0)).find()) { throw new InputTypeException("Input must contain alphanumeric value."); } logger.log("EVENT: " + gson.toJson(event)); logger.log("EVENT TYPE: " + event.getClass().toString()); response = “Hello ” + event.get(0); return response; }
For runtime errors and other function errors, the Lambda service does not return an error code. It returns a 2xx series status code indicating that the Lambda service processed the request. Instead of an error code, Lambda sends back the error with the response header X-Amz-Function-Error.
Lambda also integrates with the AWS X-Ray service to enable tracing and debugging Lambda applications. By simply adding the X-Ray SDK library to the build configuration, you can record errors and latency for any call that your function makes to an AWS service. It creates a map showing the flow of the request for each invocation.
To create an X-Ray map and trace, enable the service using the AWS console. It will add the required permissions to have Lambda functions have access to the X-Ray daemon. To record the details about each call that your function makes to other resources and services like S3, DynamoDB, you need to add the X-Ray SDK for Java to your build configuration.
AWS Lambda has become the most common service used to build serverless applications. And with support for Java, the most used language, it has become more powerful. Using Java, developers can easily build Lambda functions with the rich features available in Java libraries. The AWS SDK is continuously updated with the latest Java features to make it a more robust platform for building serverless applications.