Introduction
In part 5, we introduced AWS Lambda Web Adapter. In this article, we'll take a look at how to write an AWS Lambda function with Java 21 runtime and AWS Lambda Web Adapter using Spring Boot 3.2 version. To use the newer version of Spring Boot (i.e., 3.3), it should be enough to update the version in pom.xml.
How to write AWS Lambda with AWS Lambda Web Adapter using Spring Boot 3.2
For the sake of explanation, we'll use our Spring Boot 3.2 sample application and use Java 21 runtime for our Lambda functions.
In this application, we'll create and retrieve products and use DynamoDB as the NoSQL database. You can find the DynamoProductDao.java implementation here. We also put Amazon API Gateway in front of it as defined in AWS SAM template.
Spring Boot Product Controller annotated with @RestController and
@EnableWebMvc defines getProductById and createProduct methods.
@RequestMapping(path = "/products/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Optional<Product> getProductById(@PathVariable("id") String id) {
return productDao.getProduct(id);
}
@RequestMapping(path = "/products/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
public void createProduct(@PathVariable("id") String id, @RequestBody Product product) {
product.setId(id);
productDao.putProduct(product);
}
The mapping of the controller methods to the definition of the Lambda function happens in the SAM template.
For example getProductById method is mapped to the following Lambda function :
GetProductByIdFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: GetProductByIdWithSpringBoot32
....
Events:
GetRequestById:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /v1/products/{id}
Method: get
We see the same HTTP method "get" and the same sub-path "/products/{id}" in the Lambda function event (I'll explain the meaning of the v1/ prefix later) and in the request mapping of the getProductById method. It works similarly for the mapping of the createProduct method in the rest controller.
Until this point in time, the application based on AWS Lambda Web Adapter looks the same as the one based on AWS Serverless Java Container, as we can reuse our application based on Spring Boot.
Now come the differences. They are mainly in the AWS SAM template.
1) We need to attach the Lambda Web Adapter as a Lambda layer to our Lambda functions. Here is the example for GetProductByIdWithSpringBoot32WithLambdaWebAdapter Lambda function :
GetProductByIdFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: GetProductByIdWithSpringBoot32WithLambdaWebAdapter
Layers:
- !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:20
For Lambda function running on the arm64 architecture (which currently doesn't support Lambda SnapStart), there is another Lambda Layer to be attached: arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerArm64:20
2) In the Globals: Function: section of the SAM template, we need to define the following for all Lambda functions:
Globals:
Function:
Handler: run.sh
CodeUri: target/aws-spring-boot-3.2-lambda-web-adapter-1.0.0-SNAPSHOT.jar
Runtime: java21
....
Environment:
Variables:
....
RUST_LOG: info
REMOVE_BASE_PATH: /v1
AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
What we are doing here is to configure the Lambda environment variable AWS_LAMBDA_EXEC_WRAPPER to /opt/bootstrap. When we add a layer to a Lambda function, Lambda extracts the layer contents into the /opt directory in our function’s execution environment. All natively supported Lambda runtimes include paths to specific directories within the /opt directory. This gives our Lambda function access to our layer content. For more information about these specific paths, see Packaging your layer content.
We also set a function handler to our web application start-up script (instead of the Java Lambda Handler class). e.g. run.sh.
In the run.sh script we put everything (jars) in the lib folder into the class path and start our Spring Boot application main class software.amazonaws.Application. In the case of Spring Boot, this is the class annotated with @SpringBootApplication.
#!/bin/sh
exec java -cp "./:lib/*" "software.amazonaws.Application"
AWS_LWA_REMOVE_BASE_PATH / REMOVE_BASE_PATH - The value of this environment variable tells the adapter whether the application is running under a base path. For the detailed explanation of this parameter, please read the documentation on the AWS Lambda Web Adapter main page.
Because of this definition of the REMOVE_BASE_PATH: /v1, the Path variable of the API Gateway mapping to each Lambda function also needs to start with /v1, like Path: /v1/products/{id} for the sample for the Lambda function GetProductByIdWithSpringBoot32WithLambdaWebAdapter below
GetProductByIdFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: GetProductByIdWithSpringBoot32WithLambdaWebAdapter
....
Events:
GetRequestById:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /v1/products/{id}
Method: get
Then we need to deploy the application with sam deploy -g, and to retrieve the existing product, we have to invoke the following:
curl -H "X-API-Key: a6ZbcDefQW12BN56WED2"
https://{$API_GATEWAY_URL}/prod/v1/products/1
Conclusion
In this article, we took a look at how to write AWS Lambda functions with Java 21 runtime with AWS Lambda Web Adapter using Spring 3.2 version. As we explored, we can reuse the Spring Boot Rest Controller, as was the case with the AWS Serverless Java Container.
In the next article of the series, we'll measure the cold and warm start times for this sample application, including enabling SnapStart on the Lambda function, but also applying priming for the DynamoDB invocation.
Update from February 17, 2025: new measurements with Spring Boot 3.4 and AWS Lambda Web Adapter published in the article Spring Boot 3.4 application on AWS Lambda- Part 2 AWS Lambda Web Adapter.

Top comments (1)
Hi @vkazulkin - very nice article.