Spring Boot 3 application on AWS Lambda - Part 3 Develop application with AWS Serverless Java Container
Tags: aws, java, serverless, springboot
Published: 2024-04-15 15:19:31

## Introduction

In the [part 2](https://dev.to/aws-builders/spring-boot-3-application-on-aws-lambda-part-2-introduction-to-aws-serverless-java-container-144) of the series we introduced AWS Serverless Java Container. In this article we'll take a look into how to write AWS Lambda function with AWS Serverless Java Container using Spring Boot 3 version and Java 21 runtime. In the time of writing this article, the newest version of [Spring Boot is 3.2](https://spring.io/blog/2023/11/23/spring-boot-3-2-0-available-now/) which became available end of November 2023. 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 AWS Serverless Java Container using Spring Boot 3.2

For the sake of explanation we'll use our Spring Boot 3.2  [sample application](https://github.com/Vadym79/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2) 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/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/src/main/java/software/amazonaws/example/product/entity/Product.java) and use DynamoDB as the NoSQL database. You can find the DynamoProductDao implementation [here](https://github.com/Vadym79/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/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/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/template.yaml).

Spring Boot [Product Controller](https://github.com/Vadym79/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/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);
	}
```

The key dependency to make it work and translate between Spring Boot 3 (web annotation) model and AWS Lambda is the dependency to the artifact **aws-serverless-java-container-springboot3** defined in the [pom.xml](https://github.com/Vadym79/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/pom.xml). It's based on [Serverless Java Container](https://github.com/aws/serverless-java-container) which natively supports API Gateway's proxy integration models for requests and responses, and we can create and inject custom models for methods that use custom mappings.

```
    <dependency>
      <groupId>com.amazonaws.serverless</groupId>
      <artifactId>aws-serverless-java-container-springboot3</artifactId>
      <version>2.0.0</version>
    </dependency>
```
If we look into the whole dependency tree, we'll discover another 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 which 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) functionallity. We'll look deeper into the capabilities of Spring Cloud Function on AWS Lambda in one of my upcoming articles.

The simplest way to wire everything together is to define in the [AWS SAM template](https://github.com/Vadym79/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/template.yaml) generic **SpringDelegatingLambdaContainerHandler** handler from the artifact **aws-serverless-java-container-springboot3** and define the main class of our SpringBoot application (the class annotated with @SpringBootApplication) :

```
Handler: com.amazonaws.serverless.proxy.spring.SpringDelegatingLambdaContainerHandler 
Environment:
  Variables:
    MAIN_CLASS: com.amazonaws.serverless.sample.springboot3.Application
```
SpringDelegatingLambdaContainerHandler plays the role of proxy, receives all requests and forwards them to the correct method of our Spring Boot [Product Rest Controller](https://github.com/Vadym79/AWSLambdaJavaSnapStart/blob/main/spring-boot-3.2/src/main/java/software/amazonaws/example/product/controller/ProductController.java).

Another way to wire everything together is to implement our own [StreamLambdaHandler](https://github.com/Vadym79/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/src/main/java/software/amazonaws/example/product/handler/StreamLambdaHandler.java) which is the implementation of the com.amazonaws.services.lambda.runtime.**RequestStreamHandler** interface.

```
Globals:
  Function:
    Handler: software.amazonaws.example.product.handler.StreamLambdaHandler::handleRequest
```
StreamLambdaHandler as custom generic proxy receives then all requests and forwards them to the correct method of our Spring Boot [Product Rest Controller](https://github.com/Vadym79/AWSLambdaJavaSnapStart/blob/main/spring-boot-3.2/src/main/java/software/amazonaws/example/product/controller/ProductController.java).

In the StreamLambdaHandler we first instantiate **SpringBootLambdaContainerHandler** handler

```
SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler =
SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
```

and then proxy request through it. This approach is preferable in case we intend to implement own logic there, like we'll see with Lambda SnapStart priming in the next article.

```
  @Override
  public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
            throws IOException {
    handler.proxyStream(inputStream, outputStream, context);
 }
```
In that step the input streams will be forwarded to the intended method of the [Product Controller](https://github.com/Vadym79/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/src/main/java/software/amazonaws/example/product/controller/ProductController.java). As the last step we need to define this StreamLambdaHandler.java in the [AWS SAM template](https://github.com/Vadym79/AWSLambdaJavaSnapStart/tree/main/spring-boot-3.2/template.yaml).

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 functions with Java 21 runtime with AWS Serverless Java Container  using Spring 3.2 version. As we explored, we can re-use the Spring Boot Rest Controller. The can either re-use existing SpringDelegatingLambdaContainerHandler or to write our custom RequestStreamHandler implementation (in our case StreamLambdaHandler) which proxies the invocation to the write method of the Spring Boot Rest Controller annotated with @RequestMapping or @GET, @PUT and so on.

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 also applying various priming techniques like priming the DynamoDB invocation and priming the whole web request.