Spring Boot 3 application on AWS Lambda - Part 6 Develop application with AWS Lambda Web Adapter
Tags: aws, java, serverless, springboot
Published: 2024-05-27 15:01:43

## Introduction
In the [part 5](https://dev.to/aws-builders/spring-boot-3-application-on-aws-lambda-part-5-introduction-to-aws-lambda-web-adapter-m21) 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](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/tree/master/spring-boot-3.2-with-lambda-web-adapter) and use Java 21 runtime for our Lambda functions. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xyor3phon8sh00of5xsl.png)

In this application we'll create and retrieve [products](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/tree/master/spring-boot-3.2-with-lambda-web-adapter/src/main/java/software/amazonaws/example/product/entity/Product.java) and use DynamoDB as the NoSQL database. You can find the DynamoProductDao.java implementation [here](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/tree/master/spring-boot-3.2-with-lambda-web-adapter/src/main/java/software/amazonaws/example/product/dao/DynamoProductDao.java). We also put Amazon API Gateway in front of it as defined in [AWS SAM template](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/tree/master/spring-boot-3.2-with-lambda-web-adapter/template.yaml).

Spring Boot [Product Controller](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/tree/master/spring-boot-3.2-with-lambda-web-adapter/src/main/java/software/amazonaws/example/product/controller/ProductController.java) 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);
      }
```
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](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/tree/master/spring-boot-3.2-with-lambda-web-adapter/template.yaml).

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](https://docs.aws.amazon.com/lambda/latest/dg/packaging-layers.html).

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](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-lambda-web-adapter/src/main/resources/run.sh) we put everything (jars) in the lib folder into the class path and start our Spring Boot application main class [software.amazonaws.Application](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-lambda-web-adapter/src/main/java/software/amazonaws/Application.java). 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](https://github.com/awslabs/aws-lambda-web-adapter).

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.