Define Lambda function handler in Java
The Lambda function handler is the method in your function code that processes events. When your function is invoked, Lambda runs the handler method. Your function runs until the handler returns a response, exits, or times out.
The GitHub repo for this guide provides easy-to-deploy sample applications that demonstrate a variety of handler types. For details, see the end of this topic.
Sections
Example handler: Java 17 runtimes
In the following Java 17 example, a class named HandlerIntegerJava17
defines a
handler method named handleRequest
. The handler method takes in the following
inputs:
-
An
IntegerRecord
, which is a custom Java recordthat represents event data. In this example, we define IntegerRecord
as follows:record IntegerRecord(int x, int y, String message) { }
-
A context object, which provides methods and properties that provide information about the invocation, function, and execution environment.
Suppose we want to write a function that logs the message
from the input
IntegerRecord
, and returns the sum of x
and y
.
The following is the function code:
Example HandlerIntegerJava17.java
package example; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.RequestHandler; // Handler value: example.HandlerInteger public class HandlerIntegerJava17 implements RequestHandler<IntegerRecord, Integer>{ @Override /* * Takes in an InputRecord, which contains two integers and a String. * Logs the String, then returns the sum of the two Integers. */ public Integer handleRequest(IntegerRecord event, Context context) { LambdaLogger logger = context.getLogger(); logger.log("String found: " + event.message()); return event.x() + event.y(); } } record IntegerRecord(int x, int y, String message) { }
You specify which method you want Lambda to invoke by setting the handler parameter on your function's configuration. You can express the hander in the following formats:
-
– Full format. For example:package
.Class
::method
example.Handler::handleRequest
. -
– Abbreviated format for classes that implement a handler interface. For example:package
.Class
example.Handler
.
When Lambda invokes your handler, the Lambda runtime receives an event as a JSON-formatted string and converts it into an object. For the previous example, a sample event might look like the following:
Example event.json
{ "x": 1, "y": 20, "message": "Hello World!" }
You can save this file and test your function locally with the following AWS Command Line Interface (CLI) command:
aws lambda invoke --function-name
function_name
--payload file://event.json out.json
Example handler: Java 11 runtimes and below
Lambda supports records in Java 17 and later runtimes. In all Java runtimes, you can use a class to represent event data. The following example takes a list of integers and a context object as input, and returns the sum of all integers in the list.
Example Handler.java
In the following example, a class named Handler
defines a handler method named
handleRequest
. The handler method takes an event and context object as input and returns a
string.
Example HandlerList.java
package example; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.RequestHandler; import java.util.List; // Handler value: example.HandlerList public class HandlerList implements RequestHandler<List<Integer>, Integer>{ @Override /* * Takes a list of Integers and returns its sum. */ public Integer handleRequest(List<Integer> event, Context context) { LambdaLogger logger = context.getLogger(); logger.log("EVENT TYPE: " + event.getClass().toString()); return event.stream().mapToInt(Integer::intValue).sum(); } }
For more examples, see Sample handler code.
Initialization code
Lambda runs your static code and the class constructor during the
initialization phase before invoking your function for the first time. Resources created
during initialization stay in memory between invocations and can be reused by the handler
thousands of times. Thus, you can add
initialization code
In the following example, the client initialization code is outside the main handler method. The runtime initializes the client before the function serves its first event. Subsequent events are much faster because Lambda doesn't need to initialize the client again.
Example Handler.java
package example; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.amazonaws.services.lambda.runtime.RequestHandler; import java.util.Map; import software.amazon.awssdk.services.lambda.LambdaClient; import software.amazon.awssdk.services.lambda.model.GetAccountSettingsResponse; import software.amazon.awssdk.services.lambda.model.LambdaException; // Handler value: example.Handler public class Handler implements RequestHandler<Map<String,String>, String> { private static final LambdaClient lambdaClient = LambdaClient.builder().build(); @Override public String handleRequest(Map<String,String> event, Context context) { LambdaLogger logger = context.getLogger(); logger.log("Handler invoked"); GetAccountSettingsResponse response = null; try { response = lambdaClient.getAccountSettings(); } catch(LambdaException e) { logger.log(e.getMessage()); } return response != null ? "Total code size for your account is " + response.accountLimit().totalCodeSize() + " bytes" : "Error"; } }
Choosing input and output types
You specify the type of object that the event maps to in the handler method's signature. In the preceding
example, the Java runtime deserializes the event into a type that implements the
Map<String,String>
interface. String-to-string maps work for flat events like the
following:
Example Event.json – Weather data
{ "temperatureK": 281, "windKmh": -3, "humidityPct": 0.55, "pressureHPa": 1020 }
However, the value of each field must be a string or number. If the event includes a field that has an object as a value, the runtime can't deserialize it and returns an error.
Choose an input type that works with the event data that your function processes. You can use a basic type, a generic type, or a well-defined type.
Input types
-
Integer
,Long
,Double
, etc. – The event is a number with no additional formatting—for example,3.5
. The runtime converts the value into an object of the specified type. -
String
– The event is a JSON string, including quotes—for example,"My string."
. The runtime converts the value (without quotes) into aString
object. -
,Type
Map<String,
etc. – The event is a JSON object. The runtime deserializes it into an object of the specified type or interface.Type
> -
List<Integer>
,List<String>
,List<Object>
, etc. – The event is a JSON array. The runtime deserializes it into an object of the specified type or interface. -
InputStream
– The event is any JSON type. The runtime passes a byte stream of the document to the handler without modification. You deserialize the input and write output to an output stream. -
Library type – For events sent by AWS services, use the types in the aws-lambda-java-events library.
If you define your own input type, it should be a deserializable, mutable plain old Java object (POJO), with a default constructor and properties for each field in the event. Keys in the event that don't map to a property as well as properties that aren't included in the event are dropped without error.
The output type can be an object or void
. The runtime serializes return values into text. If the
output is an object with fields, the runtime serializes it into a JSON document. If it's a type that wraps a
primitive value, the runtime returns a text representation of that value.
Handler interfaces
The aws-lambda-java-core
The RequestHandler
interface is a generic type that takes two parameters: the input type and the
output type. Both types must be objects. When you use this interface, the Java runtime deserializes the event into
an object with the input type, and serializes the output into text. Use this interface when the built-in
serialization works with your input and output types.
Example Handler.java –
Handler interface
// Handler value: example.Handler public class Handler implements RequestHandler<Map<String,String>, String>{ @Override
public String handleRequest(Map<String,String> event, Context context)
To use your own serialization, implement the RequestStreamHandler
interface. With this interface,
Lambda passes your handler an input stream and output stream. The handler reads bytes from the input stream, writes
to the output stream, and returns void.
The following Java 21 example shows how you might use a Lambda function to process orders. The example uses buffered reader and writer types to work with the input and output streams, and shows how you can define custom Java records to use within your function.
Example HandlerStream.java
import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; public class HandlerStream implements RequestStreamHandler { private static final ObjectMapper objectMapper = new ObjectMapper(); @Override public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { Order order = objectMapper.readValue(input, Order.class); processOrder(order); OrderAccepted orderAccepted = new OrderAccepted(order.orderId); objectMapper.writeValue(output, orderAccepted); } private void processOrder(Order order) { // business logic } public record Order(@JsonProperty("orderId") String orderId, @JsonProperty("items") List<Item> items) { } public record Item(@JsonProperty("name") String name, @JsonProperty("quantity") Integer quantity) { } public record OrderAccepted(@JsonProperty("orderId") String orderId) { } }
Code best practices for Java Lambda functions
Adhere to the guidelines in the following list to use best coding practices when building your Lambda functions:
-
Separate the Lambda handler from your core logic. This allows you to make a more unit-testable function.
-
Control the dependencies in your function's deployment package. The AWS Lambda execution environment contains a number of libraries. To enable the latest set of features and security updates, Lambda will periodically update these libraries. These updates may introduce subtle changes to the behavior of your Lambda function. To have full control of the dependencies your function uses, package all of your dependencies with your deployment package.
-
Minimize the complexity of your dependencies. Prefer simpler frameworks that load quickly on execution environment startup. For example, prefer simpler Java dependency injection (IoC) frameworks like Dagger
or Guice , over more complex ones like Spring Framework . -
Minimize your deployment package size to its runtime necessities. This will reduce the amount of time that it takes for your deployment package to be downloaded and unpacked ahead of invocation. For functions authored in Java, avoid uploading the entire AWS SDK library as part of your deployment package. Instead, selectively depend on the modules which pick up components of the SDK you need (e.g. DynamoDB, Amazon S3 SDK modules and Lambda core libraries
).
-
Take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside of the function handler, and cache static assets locally in the
/tmp
directory. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time.To avoid potential data leaks across invocations, don’t use the execution environment to store user data, events, or other information with security implications. If your function relies on a mutable state that can’t be stored in memory within the handler, consider creating a separate function or separate versions of a function for each user.
-
Use a keep-alive directive to maintain persistent connections. Lambda purges idle connections over time. Attempting to reuse an idle connection when invoking a function will result in a connection error. To maintain your persistent connection, use the keep-alive directive associated with your runtime. For an example, see Reusing Connections with Keep-Alive in Node.js.
-
Use environment variables to pass operational parameters to your function. For example, if you are writing to an Amazon S3 bucket, instead of hard-coding the bucket name you are writing to, configure the bucket name as an environment variable.
-
Avoid using recursive invocations in your Lambda function, where the function invokes itself or initiates a process that may invoke the function again. This could lead to unintended volume of function invocations and escalated costs. If you see an unintended volume of invocations, set the function reserved concurrency to
0
immediately to throttle all invocations to the function, while you update the code. -
Do not use non-documented, non-public APIs in your Lambda function code. For AWS Lambda managed runtimes, Lambda periodically applies security and functional updates to Lambda's internal APIs. These internal API updates may be backwards-incompatible, leading to unintended consequences such as invocation failures if your function has a dependency on these non-public APIs. See the API reference for a list of publicly available APIs.
-
Write idempotent code. Writing idempotent code for your functions ensures that duplicate events are handled the same way. Your code should properly validate events and gracefully handle duplicate events. For more information, see How do I make my Lambda function idempotent?
.
-
Avoid using the Java DNS cache. Lambda functions already cache DNS responses. If you use another DNS cache, then you might experience connection timeouts.
The
java.util.logging.Logger
class can indirectly enable the JVM DNS cache. To override the default settings, set networkaddress.cache.ttlto 0 before initializing logger
. Example:public class MyHandler { // first set TTL property static{ java.security.Security.setProperty("networkaddress.cache.ttl" , "0"); } // then instantiate logger var logger = org.apache.logging.log4j.LogManager.getLogger(MyHandler.class); }
-
Reduce the time it takes Lambda to unpack deployment packages authored in Java by putting your dependency
.jar
files in a separate /lib directory. This is faster than putting all your function’s code in a single jar with a large number of.class
files. See Deploy Java Lambda functions with .zip or JAR file archives for instructions.
Sample handler code
The GitHub repository for this guide includes sample applications that demonstrate the use of various handler types and interfaces. Each sample application includes scripts for easy deployment and cleanup, an AWS SAM template, and supporting resources.
Sample Lambda applications in Java
-
java17-examples
– A Java function that demonstrates how to use a Java record to represent an input event data object. -
java-basic
– A collection of minimal Java functions with unit tests and variable logging configuration. -
java-events
– A collection of Java functions that contain skeleton code for how to handle events from various services such as Amazon API Gateway, Amazon SQS, and Amazon Kinesis. These functions use the latest version of the aws-lambda-java-events library (3.0.0 and newer). These examples do not require the AWS SDK as a dependency. -
s3-java
– A Java function that processes notification events from Amazon S3 and uses the Java Class Library (JCL) to create thumbnails from uploaded image files. -
Use API Gateway to invoke a Lambda function – A Java function that scans a Amazon DynamoDB table that contains employee information. It then uses Amazon Simple Notification Service to send a text message to employees celebrating their work anniversaries. This example uses API Gateway to invoke the function.
The java-events
and s3-java
applications take an AWS service event as input and
return a string. The java-basic
application includes several types of handlers:
-
Handler.java
– Takes a Map<String,String>
as input. -
HandlerInteger.java
– Takes an Integer
as input. -
HandlerList.java
– Takes a List<Integer>
as input. -
HandlerStream.java
– Takes an InputStream
andOutputStream
as input. -
HandlerString.java
– Takes a String
as input. -
HandlerWeatherData.java
– Takes a custom type as input.
To test different handler types, just change the handler value in the AWS SAM template. For detailed instructions, see the sample application's readme file.