Spring Boot 3 application on AWS Lambda - Part 9 Develop application with Spring Cloud Function AWS
Tags: java, aws, springboot, serverless
Published: 2024-07-08 15:07:52

## Introduction
In the [part 8](https://dev.to/aws-builders/spring-boot-3-application-on-aws-lambda-part-8-introduction-to-spring-cloud-function-99a) we introduced concepts behind Spring Cloud Function (AWS). In this article we'll take a look into how to write AWS Lambda function with Java 21 runtime and Spring Cloud Function AWS using Spring Boot 3.2 version. To use the newer version of Spring Boot (i.e. 3.3) it will maybe be enough to update the version in pom.xml.

## How to write AWS Lambda with Spring Cloud Function AWS 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-spring-cloud-function) and use Java 21 runtime for our Lambda functions. 

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

There are multiple ways to write AWS Lambda with Spring Cloud Function AWS using Spring Boot 3.2 :

- Broadly speaking AWS Lambda with AWS Serverless Java Container introduced in the article [Develop application with AWS Serverless Java Container](https://dev.to/aws-builders/spring-boot-3-application-on-aws-lambda-part-3-develop-application-with-aws-serverless-java-container-2901) can also be considered as Spring Cloud Function AWS application because of dependency [spring-cloud-function-serverless-web](https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-serverless-web) which aws-serverless-java-container-springboot3 requires. This is the collaboration effort between Spring and AWS Serverless developers. It provides [Spring Cloud Function on AWS Lambda](https://docs.spring.io/spring-cloud-function/reference/adapters/aws-intro.html) functionality.

- We can also define Spring Beans (annotated with @ Bean) directly in the main Spring Boot Application class (annotated with @SpringBootApplication) and map the method name annotated with @ Bean (which is the default bean name) to exactly match the Lambda function name in the Infrastructure as a Code (i.e. AWS SAM template). Example of such approach can be found [here](https://github.com/olegz/spring-aws-2023/tree/main/scf-aws).
- Another approach is to use org.springframework.cloud.function.adapter.aws.web.WebProxyInvoker::handleRequest Lambda handler and normal Spring Boot RestController (annotated with @RestController). Example of such approach can be found [here](https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-serverless-web/sample/pet-store).
- In this article I'd like to show more classical approach how to convert Java 8 Function interface into AWS Lambda function.

In the [sample application](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/tree/master/spring-boot-3.2-with-spring-cloud-function) we'll create and retrieve [products](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-spring-cloud-function/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/blob/master/spring-boot-3.2-with-spring-cloud-function/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/blob/master/spring-boot-3.2-with-spring-cloud-function/template.yaml).

In the [pom.xml](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-spring-cloud-function/pom.xml) we need to define these  dependencies among others:

```
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-function-web</artifactId>
</dependency>
```
which will convert Spring Cloud Function into AWS Lambda and expose it as web application.

This is our [Spring Boot main class](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-spring-cloud-function/src/main/java/software/amazonaws/Application.java) which we also have to have to additionally define in the [SAM template](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-spring-cloud-function/template.yaml) in the environment variables section:

```
Globals:
  Function:
    .....
    Environment:
      Variables:
        MAIN_CLASS: software.amazonaws.Application
```
Next we implement our [GetProductByIdHandler](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-spring-cloud-function/src/main/java/software/amazonaws/example/product/handler/GetProductByIdHandler.java) which implements java.util.function.Function interface and has to be annotated with Spring @Component annotation. 

```
@Component
public class GetProductByIdHandler implements 
Function<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

      @Autowired
      private DynamoProductDao productDao;
      ...

      public APIGatewayProxyResponseEvent apply(APIGatewayProxyRequestEvent requestEvent) {
      String id = requestEvent.getPathParameters().get("id");
      Optional<Product> optionalProduct = productDao.getProduct(id);
      return new APIGatewayProxyResponseEvent().
              withStatusCode(HttpStatusCode.OK).    
              withBody(....);
  }
```

@Component is an annotation that allows Spring to detect our custom beans automatically. By default, Spring takes the simple name of the type (Java class) declaring the bean, changes the first letter to lowercase, and uses the resulting value to name the bean.

We can implement this Lambda function also without certain AWS dependencies by returning Product or Optional<Product> directly instead of wrapping it into APIGatewayProxyResponseEvent object.

Next we define the Lambda function mapping in [SAM template](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-spring-cloud-function/template.yaml)
 
```
 GetProductByIdFunction:
    Type: AWS::Serverless::Function
    Properties:
      Environment:
        Variables:
           SPRING_CLOUD_FUNCTION_DEFINITION: getProductByIdHandler
      FunctionName: GetProductByIdWithSpringBoot32SCF
      ...
      Events:
        GetRequestById:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /products/{id}
            Method: get            

```
The relevant part ist here the environment variable SPRING_CLOUD_FUNCTION_DEFINITION which is the Spring bean name which in our case corresponds to the Lambda function class name with the first letter changed to lowercase. In our example Lambda function with environment variable SPRING_CLOUD_FUNCTION_DEFINITION = **getProductByIdHandler** is mapped to the Lambda Java class **GetProductByIdHandler**.

Note that since AWS does not allow dots . and/or hyphens - in the name of the environment variable, we can benefit from Spring Boot support and simply substitute dots with underscores and hyphens with camel case. So for example spring.cloud.function.definition becomes spring_cloud_function_definition. We can also write the variable name i.e. spring.cloud.function.definition and spring.cloud.function.routing-expression using all capital letters.

Also in the SAM template in the global Lambda function environment variables we define the generic Spring Cloud Function AWS Lambda handler

```
Globals:
  Function:
    Handler: org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest
```
which routes all requests to the correct Lambda implementation (we defined separate Lambda Handler Java class for each Lambda function implementation).  

You can learn more about Spring Cloud Function routing and filtering capabilities in this [article](https://docs.spring.io/spring-cloud-function/docs/4.0.5/reference/html/spring-cloud-function.html#_function_routing_and_filtering).

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: a6ZbcDefQW12BN56WEA7"
https://{$API_GATEWAY_URL}/prod/products/1

```


## Conclusion

In this article we took a look into how to write AWS Lambda function with Java 21 runtime with Spring Cloud Function AWS using Spring Boot 3 version. In comparison to AWS Serverless Java Container and AWS Lambda Web Adapter, Spring Cloud Function AWS doesn't strictly require but supports reusing Spring Boot Rest Controller. In our main example of using Spring Cloud Function AWS we exposed Java 8 Function as Lambda function.

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 and also apply various priming techniques for the DynamoDB invocation.