Spring Boot 3 application on AWS Lambda – Part 3 Develop application with AWS Serverless Java Container

Spring Boot 3 application on AWS Lambda – Part 3 Develop application with AWS Serverless Java Container

Introduction

In the part 2 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 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 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 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 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. It’s based on 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 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 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 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.

Another way to wire everything together is to implement our own StreamLambdaHandler 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.

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. As the last step we need to define this StreamLambdaHandler.java in the AWS SAM template.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *