Partner – Orkes – NPI EA (cat=Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag=Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – LambdaTest – NPI EA (cat=Testing)
announcement - icon

Accessibility testing is a crucial aspect to ensure that your application is usable for everyone and meets accessibility standards that are required in many countries.

By automating these tests, teams can quickly detect issues related to screen reader compatibility, keyboard navigation, color contrast, and other aspects that could pose a barrier to using the software effectively for people with disabilities.

Learn how to automate accessibility testing with Selenium and the LambdaTest cloud-based testing platform that lets developers and testers perform accessibility automation on over 3000+ real environments:

Automated Accessibility Testing With Selenium

eBook – Guide Spring Cloud – NPI (cat=Cloud/Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

1. Overview

AWS Lambda is a serverless computing service provided by Amazon. It’s a powerful tool that helps us build scalable event-driven applications.

The serverless nature of AWS Lambda allows us to focus on our business logic, while AWS takes care of dynamic allocation and provisioning of servers. It’s also a cost-effective solution as we only pay for the actual execution time and memory consumption of our code.

In this tutorial, we’ll explore how to create a basic AWS Lambda function using Java. We’ll cover the necessary dependencies, different ways of creating our Lambda function, building the deployment file, and testing our Lambda function locally using LocalStack.

To follow this tutorial, we’ll need an active AWS account.

2. Dependencies

Let’s start by adding the Lambda core dependency to our project’s pom.xml file:

<dependency> 
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-core</artifactId>
    <version>1.2.3</version>
</dependency>

Next, we’ll need to add the Maven Shade Plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <createDependencyReducedPom>false</createDependencyReducedPom>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
	    <goals>
                <goal>shade</goal>
            </goals>
        </execution>
    </executions>
</plugin>

The Maven Shade Plugin is essential when building AWS Lambda functions with Java. It allows us to package our application and its dependencies into a single, self-contained JAR file, also known as an “uber” or “fat” JAR.

The plugin extracts the content of all our dependencies and puts them with the classes of our project, which is how AWS Lambda expects us to deploy our code.

We can create our fat JAR in the target directory of our project by executing:

mvn clean package

When using Gradle, we can create our fat JAR using the Gradle Shadow Plugin.

3. Creating a Handler

The entry point for any AWS Lambda function is a handler method. It processes the incoming request and returns a response.

When creating a Lambda function, we have to specify our handler. We do this using the format package.ClassName. We’ll look at how to specify this configuration in the next sections where we test and deploy our Lambda function.

We have a few different options when it comes to defining our handler method and we’ll explore them in this section.

3.1. Implementing the RequestHandler Interface

The most common and recommended way to define a handler is by implementing the RequestHandler interface and overriding its handleRequest() method:

class LambdaHandler implements RequestHandler<Request, Response> {

    @Override
    public Response handleRequest(Request request, Context context) {
        LambdaLogger logger = context.getLogger();
        logger.log("Processing question from " + request.name(), LogLevel.INFO);
        return new Response("Subscribe to Baeldung Pro: baeldung.com/members");
    }
}

record Request(String name, String question) {}

record Response(String answer) {}

The Request and Response are simple records that represent the input and output of our Lambda function. We also specify these types as generic parameters in the RequestHandler interface.

The handleRequest() method takes our Request record and a Context object as parameters. The Context parameter provides useful information about the Lambda execution environment, including a LambdaLogger that we can use for logging.

3.2. Implementing the RequestStreamHandler Interface

Another approach to define a handler is to implement the RequestStreamHandler interface:

class LambdaStreamHandler implements RequestStreamHandler {

    @Override
    public void handleRequest(InputStream input, OutputStream output, Context context) {
        ObjectMapper mapper = new ObjectMapper();
        Request request = mapper.readValue(input, Request.class);

        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output))) {
            writer.write("Hello " + request.name() + ", Baeldung has great Java content for you!");
            writer.flush();
        }
    }

    record Request(String name) {}
}

Here, we implement the RequestStreamHandler interface and override the handleRequest() method. Using ObjectMapper, we deserialize the raw request data from the InputStream and write our response to the OutputStream.

This interface is useful when working with raw input and output streams.

3.3. Custom Handler Method

Lastly, we can define a custom handler method:

class CustomLambdaHandler {

    public Response handlingRequestFreely(Request request, Context context) {
        LambdaLogger logger = context.getLogger();
        logger.log(request.name() + " has invoked the lambda function", LogLevel.INFO);
        return new Response("Subscribe to Baeldung Pro: baeldung.com/members");
    }

    record Request(String name) {}

    record Response(String answer) {}
}

In this approach, we create a CustomLambdaHandler class with a handlingRequestFreely() method that takes the Request record and Context object as parameters, similar to the RequestHandler example. The only difference is that we’re not implementing any specific interface.

Unlike the previous two approaches, when creating a custom handler method, we need to use the format package.ClassName::methodName to configure our handler. For example, if our CustomLambdaHandler class is in the package com.baeldung.lambda, then we’ll specify the handler as com.baeldung.lambda.CustomLambdaHandler::handlingRequestFreely when creating our Lambda function.

4. Testing Lambda Function Locally Using LocalStack

During development, it’s often convenient to test our Lambda functions locally before deploying them to AWS. LocalStack is a popular tool that allows us to run an emulated AWS environment locally on our machine.

We’ll test our LambdaHandler class, which we created earlier by implementing the RequestHandler interface.

First, let’s start a LocalStack container using Docker:

docker run \
    --rm -it \
    -p 127.0.0.1:4566:4566 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v ./target:/opt/code/localstack/target \
    localstack/localstack

We map the required port and mount our project’s target directory, which contains our fat JAR, into the container. It’s important to note that there are other ways to install Localstack as well.

Next, we’ll get into our container’s shell and create our Lambda function:

awslocal lambda create-function \
    --function-name baeldung-lambda-function \
    --runtime java21 \
    --handler com.baeldung.lambda.LambdaHandler\
    --role arn:aws:iam::000000000000:role/lambda-role \
    --zip-file fileb:///opt/code/localstack/target/java-lambda-function-0.0.1.jar

We specify Java 21 as the runtime, our handler, and the location of our JAR file in the container using the zip-file parameter.

With our function created, let’s now invoke it:

awslocal lambda invoke \
    --function-name baeldung-lambda-function \
    --payload '{ "name": "John Doe", "question": "How do I view articles ad-free and in dark mode on Baeldung?" }' output.txt

We pass our function name and JSON request payload. The response from our Lambda function is saved in the specified output.txt file, which contains:

{
    "answer": "Subscribe to Baeldung Pro: baeldung.com/members"
}

In case of any errors, the error details will also be logged in our output.txt file.

Running our Lambda functions locally with LocalStack allows us to catch issues early in the development process.

5. Deploying Lambda Function

Now that we’ve created our Lambda function and tested it locally, let’s look at how we can deploy it to our AWS environment.

We’ll use AWS CloudFormation, which allows us to define and manage our Infrastructure as Code (IaC). We’ll deploy the same Lambda function that we tested in the previous section.

5.1. Creating AWS CloudFormation Template

First, we’ll need to store our fat JAR file in an Amazon S3 bucket. This is necessary as CloudFormation references the JAR file from the specified S3 bucket during the deployment process.

Next, let’s create a generic CloudFormation template for our Lambda function:

AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda function deployment with Java 21 runtime

Parameters:
    LambdaHandler:
        Type: String
        Description: The handler for the Lambda function
    S3BucketName:
        Type: String
        Description: The name of the S3 bucket containing the Lambda function JAR file
    S3Key:
        Type: String
        Description: The S3 key (file name) of the Lambda function JAR file

Resources:
    BaeldungLambdaFunction:
        Type: AWS::Lambda::Function
        Properties:
            FunctionName: baeldung-lambda-function
            Handler: !Ref LambdaHandler
            Role: !GetAtt LambdaExecutionRole.Arn
            Code:
                S3Bucket: !Ref S3BucketName
                S3Key: !Ref S3Key
            Runtime: java21
            Timeout: 10
            MemorySize: 512
    LambdaExecutionRole:
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: 2012-10-17
                Statement:
                    - Effect: Allow
                      Principal:
                          Service: lambda.amazonaws.com
                      Action: sts:AssumeRole
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

In our CloudFormation template, we define a Lambda function named baeldung-lambda-function. We attach the managed policy AWSLambdaBasicExecutionRole to our Lambda function through the IAM role LambdaExecutionRole, granting it the necessary permissions to execute and write logs to Amazon CloudWatch.

The Parameters in our CloudFormation template allow us to define input values that we can pass to our template during stack creation. We use the !Ref function to dynamically reference these parameter values in our template to define our lambda properties.

We define a very basic Timeout of 10 seconds and MemorySize of 512 MB in our template, but it can be updated as per requirement.

5.2. Creating CloudFormation Stack

Now that we’ve defined our template, we need to create our CloudFormation stack using the AWS CLI:

aws cloudformation create-stack \
    --stack-name baeldung-cloudformation-java-21-lambda-function \
    --template-body file://java-21-lambda-function-template.yaml \
    --capabilities CAPABILITY_IAM \
    --parameters \
        ParameterKey=LambdaHandler,ParameterValue=com.baeldung.lambda.LambdaHandler\
        ParameterKey=S3BucketName,ParameterValue=baeldung-lambda-tutorials-bucket \
        ParameterKey=S3Key,ParameterValue=java-lambda-function-0.0.1.jar

In our create-stack command, we provide three parameters:

  • stack-name: to specify the name of our CloudFormation stack
  • template-body: to specify the path to our CloudFormation template file
  • parameters: to specify the parameter values for our Lambda function

We also provide the value CAPABILITY_IAM in the capabilities parameter. This is required when our template creates a new IAM resource — for example, the IAM role for our Lambda function in our example.

5.3. Triggering Lambda Function

Once our Lambda function is deployed successfully, we can invoke it via the AWS CLI:

aws lambda invoke --function-name my-lambda-function \
    --cli-binary-format raw-in-base64-out \
    --payload '{ "name": "John Doe", "question": "How do I view articles ad-free and in dark mode on Baeldung?" }' output.txt

The above command is a little different from the one we executed in our LocalStack container. However, it will behave the same and output the response to the output.txt file.

In a real-world scenario, instead of directly invoking our Lambda functions via the CLI, our Lambda functions are typically triggered by events from various AWS services, such as API Gateway and S3.

For example, we can configure API Gateway to invoke our Lambda function whenever a specific API endpoint is called, enabling us to build serverless APIs.

Another use case is triggering Lambda functions with S3 events. We can execute our business logic whenever an object is created, modified, or deleted in a specific S3 bucket. This is useful for scenarios like image processing, where we want to perform actions on newly uploaded images.

By using this event-driven nature of AWS Lambda, we can run our code to automatically react to the changes in our AWS environment.

6. Considerations for Using Java as Lambda Runtime

Before we conclude this tutorial, there are a few considerations to keep in mind before choosing Java as the runtime for our AWS Lambda function.

One of the main concerns with using Java for Lambda for time-sensitive applications is the cold start time. When a Lambda function is invoked after a period of inactivity, there is a delay in starting up the JVM and loading the necessary classes. This overhead increases the time it takes for our lambda function to complete.

Another consideration is the memory usage of Java applications. Java tends to consume more memory compared to other languages, such as Node.js or Python. This higher memory consumption can lead to increased costs if not optimized properly. It’s important to fine-tune the memory settings of our Lambda function to strike a balance between performance and cost.

Recently, GraalVM has emerged as a potential solution. It compiles our Java application into native executables, significantly reducing startup time and optimizing memory.

7. Conclusion

In this article, we’ve explored creating an AWS Lambda function using Java.

We discussed the required dependencies and plugin needed to create our executable Lambda function. We followed this by looking at the different ways we can define our handler method.

Finally, we looked at how we can test our Lambda function locally using LocalStack. Once we verified that our function executed correctly, we deployed it to our real AWS environment using AWS CloudFormation.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

Partner – Orkes – NPI EA (cat = Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag = Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

eBook Jackson – NPI EA – 3 (cat = Jackson)
eBook – eBook Guide Spring Cloud – NPI (cat=Cloud/Spring Cloud)