Spring Boot 3 application on AWS Lambda - Part 2

Exploring the AWS Lambda Web Adapter

Exploring the AWS Lambda Web Adapter

Spring Boot 3 application on AWS Lambda - Part 2

Exploring the AWS Lambda Web Adapter


In the first part of the series, we looked at the concepts behind the AWS Serverless Java Container and used it to develop a Spring Boot 3 application on AWS Lambda*This time we’ll present another way to build such applications: the AWS Lambda Web Adapter.

AWS Lambda Web Adapter is a tool written in the programming language Rust for running web applications on AWS Lambda. It enables developers to create web applications (HTTP API) with well-known frameworks (e.g. Express.js, Next.js, Flask, Spring Boot, ASP.NET and Laravel, basically anything that speaks HTTP 1.1/1.0) and run them on AWS Lambda. The same Docker image can be executed on AWS Lambda, Amazon EC2, AWS Fargate, and local computers (Fig. 1).

 architecture of the AWS Lambda Web Adapter

Fig. 1: The architecture of the AWS Lambda Web Adapter

Introduction to the AWS Lambda Web Adapter

The AWS Lambda Web Adapter includes a wide array of features, but the following are key components of this tool:

  • It runs web applications on AWS Lambda.

  • It supports the Amazon API Gateway Rest API and HTTP API endpoints, Lambda Function URLs and Application Load Balancer.

  • It supports Lambda managed runtimes, custom runtimes, and Docker OCI images.

  • It supports any web frameworks and languages without new code dependencies.

  • It enables ‘soft’ shutdown of the application.

  • It supports compression of response payloads.

  • It supports response streaming as Lambda web adapter call mode. The default mode is buffered. If you configure it as response_stream, the Lambda Web Adapter streams the response to the Lambda service. This is useful to shorten the time to the first byte if a large file or video is to be streamed to the client. More details about this function can be found on the AWS blog here and here. With response streaming, we can bypass the AWS Lambda Service (quota) limit of 6 MB for the maximum size of an incoming synchronous call request or outgoing response.

  • It supports AWS non-HTTP event triggers such as SQS, SNS, S3, DynamoDB, Kinesis, Kafka, EventBridge and Bedrock agents.

  • It supports Readiness Check Port; path and traffic port can be configured via environment variables. When a new Lambda runtime starts, the Web Adapter is started as a Lambda extension, followed by the web application. Web Adapter repeats this request every ten milliseconds until the web application returns an HTTP response (status code ≥ 100 and < 500) or the function times out. Once the readiness check has been passed, the Lambda Web Adapter starts the Lambda Runtime and forwards the calls to the web application.

  • It supports local debugging. The tool enables developers to develop web applications locally with familiar tools and debuggers: simply run and test the web application locally. If we want to simulate the Lambda runtime locally, we can use AWS SAM CLI with the sam local command.

One of the inventors of AWS Lambda Web Adapter, Harold Sun, gave a very good introduction to this tool at Re:Invent 2023 - which is available for replay here.

AWS Lambda Web Adapter can be deployed in different ways, as:

  • Lambda functions packaged as Docker images

  • Lambda functions packaged as OCI images

  • Lambda functions packaged as a zip package for AWS, which manages runtimes such as Java 21 and attached to their Lambda function as a Lambda layer. This zip package is how we will deploy the AWS Lambda Web Adapter here for our Spring Boot 3 sample application.

Employ AWS Lambda Web Adapter

We use the Spring Boot 3 sample application for our lambda functions known from Part 1 and the Java 21 Runtime (Fig. 2).

Architecture of our dummy application

Fig. 2: Architecture of our dummy application

Now, let's take a look at the relevant source code fragments: The Spring _Boot_3 ProductController class, annotated with @RestController and @EnableWebMvc, defines the methods getProductById and createProduct (Listing 1).

Listing 1

@RequestMapping(path = "/products/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)

public Optional<Produkt> 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);

}

Up to this point, the application based on AWS Lambda Web Adapter looks exactly like the one we realised with AWS Serverless Java Container. This means we can reuse the business logic of our Spring Boot-based application.

Yet some differences can be found, mainly in the Infrastructure as Code (IaC), for which we use AWS SAM Template (template.yaml). We need to add Lambda Web Adapter as a Lambda layer to our Lambda functions. Listing 2 shows the example for the Lambda function GetProductByIdWithSpringBoot32WithLambdaWebAdapter.

Listing 2

GetProductByIdFunction:

Type: AWS::Serverless::Function

Properties:

FunctionName: GetProductByIdWithSpringBoot32WithLambdaWebAdapter

Layers:

- !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:20

For lambda functions that run on the arm64 architecture a different lambda layer must be added, because it currently does not support...