Introduction
In the part 5 we introduced AWS Lambda Web Adapter. In this article we'll take a look into how to write 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 defintion 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 same sub-path "/products/{id}" in the Lambda funtion event (I'll explain the meaning of the v1/ prefix later) and in the request mapping of the getProductById method. It works similar 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 exactly the same as based on AWS Serverless Java Container as we can re-use our Spring Boot based application.
Now come the differences. They are mainly in the AWS SAM template.
1) We need to attach 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 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 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 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 into 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 re-use the Spring Boot Rest Controller as well as it was the case with 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.
Top comments (1)
Hi @vkazulkin - very nice article.