Deploying Spring Boot MVC on AWS Lambda using Serverless

Hirunika Karunathilaka
8 min readSep 25, 2021

What is covered in this article,

  • Why AWS Lambda?
  • Creating Spring Boot Project from scratch
  • Convert application to run as a Lambda function
  • Different approaches to serverless on AWS Lambda
  • Deploy to AWS using Serverless Framework

Why AWS Lambda?

The typical approach to deploy a Spring Boot application to cloud will be running the Java application on an EC2 instance continuously. The disadvantage of this way is that the EC2 instances need to be monitored and we should pay the compute capacity used by the instance when its up.

AWS Lambda is an on-demand cloud computing resource offered as function-as-a-service by AWS. It provides the service with a low cost and zero maintenance. With AWS Lambda, the computing resources scale up and back down automatically based on real-time demands. Without any provisioned or managed servers, Lambda runs our code on demand. Therefore, we are charged only when our code is triggered.

Creating Spring Boot Project from scratch

Let’s create a Spring Boot project from scratch using Spring Initializer . Give Gradle as the build tool and select Java(version 11). Also add the Spring Web dependency to create web applications using Spring MVC.

The downloaded project structure will be like below.

Now, under the “springbootserverless” package, we’ll add two packages to store model and controller classes.

User class

package com.poc.springbootserverless.models;public class User {
private String name;
private int age;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

UserController class

package com.poc.springbootserverless.controllers;import com.poc.springbootserverless.models.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@RestController
public class UserController {
@RequestMapping(path = "/users", method = RequestMethod.GET)
public List<User> listLambdaUsers() {
return Arrays.asList(new User("John", 20), new User("Mike", 25));
}
}

Now if we go to the terminal and execute below command, the project will run on localhost with default port number 8080.

$ ./gradlew bootRun

http://localhost:8080/ will give a Whitelabel error since we don’t have any endpoint implemented for path “/”. With http://localhost:8080/users endpoint, our users list will be returned as below.

[{"name":"John","age":20},{"name":"Mike","age":25}]

Convert application to run as a Lambda function

This section includes how to convert our rest API application to a lambda function.

Add dependency

First thing is we need to add the aws-serverless-java-container-spring dependency to the build.gradle file. Then go to Gradle Tool window and sync the dependencies.

Create AWS Lambda Handler

Lambda handler is a class which acts as the communication layer. This captures the requests and transfer it to the Spring Boot application. The SpringBootLambdaContainerHandler from the aws library is used to wrap our SpringBootServerlessApplication using getAwsProxyHandler method . The handleRequest method will be called each time when AWS Lambda function is invoked to pass user data and execute handle logic.

Below is the LambdaHandler class created under the springbootserverless package.

package com.poc.springbootserverless;import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import javax.ws.rs.core.Application;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class LambdaHandler implements RequestStreamHandler { private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler; static {
try {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(SpringBootServerlessApplication.class);
} catch (ContainerInitializationException e) {
// Re-throw the exception to force another cold start
e.printStackTrace();
throw new RuntimeException("Could not initialize Spring Boot application", e);
}
}
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
handler.proxyStream(inputStream, outputStream, context);
// just in case it wasn't closed by the mapper
outputStream.close();
}
}

The getAwsProxyHandler method is expecting a WebApplicationInitializer class parameter . Since the SpringBootServletInitializer class implement the WebApplicationInitializer interface, we can update the SpringBootServerlessApplication class to extend the SpringBootServletInitializer on startup to configure the things.

Different approaches to serverless on AWS Lambda

For deploying a Java application to AWS, there are several approaches to choose from.

Serverless Framework:

This is a stand-alone, free to use, opinionated tool to make working with Serverless applications quicker and easier. It’s not tied to any Cloud provider, so you can use Serverless framework to build Serverless applications on GCP, Azure and AWS. Let’s see some pros and cons and when to use.

Pros : The best thing with Serverless framework is with a few lines of configuration you create many underlying infrastructure resources which can save the effort of manually understanding and writing them.

Has a good community of plugins which allow you to quickly extend or modify your Serverless Framework configuration.

Cons: Serverless functions usually operate in a broader architectural context, requiring access to databases, queues, etc. As your architecture increases in complexity managing these additional resources in Serverless Framework will be difficult and not be the best option.

When to use: When focusing on serverless application related resources, it is better to use this framework. Basically the application concerned resources should be put in a serverless.yaml configuration.

Best if you need to start your Serverless project quickly and to get up and running quickly.

Terraform :

Terraform is a stand-alone Infrastructure As Code tool. Terraform works from a CLI and is Cloud provider agnostic and can provision infrastructure in GCP, AWS and Azure. Terraform is not a serverless specific tool and we can combine it to work with a serverless framework.

Pros: A good solution for provisioning all of your infrastructure needs without needing another tool.

Cons: Need to have a good understanding of what you’re provisioning under the hood. The setting up and learning curve is higher compared to Serverless.

When to use: When focusing on full pledged infrastructure or traditional style cloud infrastructure like defining networking, servers, storing, load balancer etc. by ourselves, it is better to go with this framework.

AWS SAM (Serverless Application Model)

AWS SAM is AWS’s response to Serverless Framework. Serverless Framework and SAM have quite similar philosophies . They both work on top of CloudFormation and have similar functionalities like allowing local deployment and easy log access.

Cloudformation

CloudFormation is the AWS custom solution for infrastructure as code. So you can think of CloudFormation as the AWS specific alternative to Terraform.

The major difference between Terraform and CloudFormation is that CloudFormation is a hosted service inside AWS rather than a stand-alone CLI.

For this demonstration, we will use the Serverless Framework from above.

Create Serverless configuration file

Let’s create a serverless.yaml under the root folder as below.

service: spring-boot-serverlessprovider:
name: aws
runtime: java11
package:
artifact: build/distributions/spring-boot-serverless-0.0.1-SNAPSHOT.zip
functions:
springBootServerless:
handler: com.poc.springbootserverless.LambdaHandler::handleRequest
events:
- http:
path: /users
method: get
timeout: 30

By writing your AWS Lambda in infrastructure in code as above we don’t need to manually create the required resources from the portal.

With this it will create a function named “springBootServerless” and it will invoke the handleRequest method which was created earlier.

We have specified the location for the built zip file which needs to be get deployed to aws. For that we need to do some modifications in the build.gradle file as below.

buildscript {
ext {
springBootVersion = '2.5.5'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
plugins {
id 'org.springframework.boot' version '2.5.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.poc'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
// Task for building the zip file for upload
task buildZip(type: Zip) {
// set the base name of the zip file
from compileJava
from processResources
into('lib') {
from configurations.runtimeClasspath
}
}
build.dependsOn buildZipwrapper {
gradleVersion = '7.1.1'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'com.amazonaws.serverless:aws-serverless-java-container-spring:1.1'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'io.symphonia:lambda-logging:1.0.1'
}
test {
useJUnitPlatform()
}

Run ./gradlew bootRun and ./gradlew build commands . Gradle will initiate a build process and a compiled jar is packed into spring-boot-serverless-0.0.1-SNAPSHOT zip.

The built zip file is available under the build->distributions folders.

Deploy to AWS using Serverless Framework

Before deploy to AWS Lambda, we need to setup Serverless framework with AWS account.

Install Serverless in machine globally,

$ npm install -g serverless

Configure AWS credentials with Serverless as below. For that we need to have the IAM user Access Key and Secret Access key.

$ serverless config credentials — provider aws — key <Accesskey> — secret <secretKey> — profile serverlessUser

Next, we’ll deploy the built zip file to AWS.

$ serverless deploy

The Serverless framework will pick this zip generated in the build process and deploy it to AWS. If the deployment is successful, it will output the created endpoint for the lambda function as below.

Serverless: Stack update finished…
Service Information
service: spring-boot-serverless
stage: dev
region: us-east-1
stack: spring-boot-serverless-dev
resources: 11
api keys:
None
endpoints:
GET — ///endpoint
functions:
springBootServerless: spring-boot-serverless-dev-springBootServerless
layers:
None

By navigating to the endpoint from a browser, the response can be seen as below.

[{“name”:”John”,”age”:20},{“name”:”Mike”,”age”:25}]

--

--