Create and Run Spring Boot REST APIs on AWS Lambda
Consider about using AWS Lambda, if you are planning to deploy your Spring Boot application in a serverless world.
AWS Lambda is a compute service where you can run any type of application code or backend service without worrying about managing servers, all with zero administration.
Lambda runs code only when it is needed and also scales automatically from a few requests per day to thousands of requests per second.
Normally, Java frameworks, such as Spring Boot embed a servlet container engine, such as Tomcat, in the built package to run on a server. Similarly, AWS provides aws-serverless-java-container library to make it easy to run Java applications written with frameworks such as Spring, Spring Boot, Jersey, Spark in AWS Lambda.
The aws-serverless-java-container library acts as a proxy between the Lambda runtime and the Java frameworks, pretend to be a servlet engine, translates incoming events to request that frameworks can understand and transforms your application responses to a format that API Gateway can understand.
The aws-serverless-java-container library is available on Maven .
There are different flavors of the aws-serverless-java-container library for Spring , Spring Boot , Jersey , Spark
Create a New Project
For this example, we will build a Spring Boot application using the Maven archetypes.
To create a new project, you will need Maven installed on your local machine.
Using your terminal, navigate to your workspace directory and execute the following maven command to create a new project from an archetype. Do not forget to replace the groupId and artifactId with your preferred settings:
cd myworkspace
mvn archetype:generate -DgroupId=my.sample.service \
-DartifactId=springboot-sample -Dversion=1.0-SNAPSHOT \
-DarchetypeGroupId=com.amazonaws.serverless.archetypes \
-DarchetypeArtifactId=aws-serverless-springboot-archetype \
-DarchetypeVersion=1.0.1 -Dinteractive=false
The maven client creates the project structure. In this example, we are using the aws-serverless-springboot-archetype to build our Spring Boot sample application. There are similar artifacts for Spring, Jersey, and Spark.
The Spring Boot Application
Open the archetype sample project in your favourite Java IDE. If you go through the generated project structure, you will find a controller class with a /ping path that returns a JSON Hello World message.
In your code package, in my case under my.sample.service, you will find a controller package with a PingController class. The PingController class is annotated with @RestController @EnableWebMvc annotations and defines a single GET method.
@RestController
@EnableWebMvc
public class PingController {
@RequestMapping(path = "/ping", method = RequestMethod.GET)
public Map ping() {
Map pong = new HashMap<>();
pong.put("pong", "Hello, World!");
return pong;
}
}
The Lambda Handler
In the main package of the sample application, you'll find StreamLambdaHandler class that implements Lambda's RequestStreamHandler interface. This class is the entry point for AWS Lambda in our application.
public class StreamLambdaHandler implements RequestStreamHandler
We use stream handler because our event models depends on annotations for marshalling and unmarshalling. Lambda's built-in serializer does not support annotations.
private static SpringBootLambdaContainerHandler handler;
static {
try {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
} catch (ContainerInitializationException e) {
// if we fail here. We re-throw the exception to force another cold start
e.printStackTrace();
throw new RuntimeException("Could not initialize Spring Boot application", e);
}
}
First, we declare a static instance of the SpringBootLambdaContainerHandler object. We initialize this object using the getAwsProxyHandler static method with Application.class inside a static block. The getAwsProxyHandler automatically creates an instance of the library to handle API Gateway's proxy integration events. We can create custom implementations of the RequestReader and ResponseWriter objects to support custom event types.
The handler variable is declared as a static class member because, we only need a single instance of this object. AWS Lambda attempts to re-use containers across invocations. The runtime holds the handler class as a singleton and the handlerRequest method is invoked each time. When Lambda starts, the Java runtime instantiates the static variables which gives better performance for the heavy introspection operations.
@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 implementation of the handler method is the main entry point for Lambda. The single calling of proxyStream method of the SpringBootLambdaContainerHandler object reads the input stream and creates an HttpServletRequest from its data. The HttpServletResponse created by our application is automatically written to the output stream in the format Amazon API Gateway expects.
The archetype generates three files: pom.xml, sam.yaml, and README.md in the project root.
The pom.xml file defines the project Maven dependencies. If you go through the file, you will see that it includes the aws-serverless-java-container-spring library.
If you look at the build section of the pom.xml file, you'll find the use of maven-shade-plugin to generate an uber-jar that you can upload to AWS Lambda.
The sam.yaml file is a Serverless Application Model (SAM) template using which you can deploy your application to AWS or test it in your local machine with SAM Local . This SAM makes it easier to define serverless stacks in code. This file defines a single resource, AWS::Serverless::Function . The function is configured to use the uber-jar that is generated by the build process and points to our handler class. The API is defined in the Event section of the function resource. When you deploy the template, an API Gateway RestApi, Stage, and Deployment are implicitly created.
The READ.me file contains a generated information to build, deploy, and test our application.
Deploy to AWS Lambda
You need to have the AWS CLI installed and configured to quickly deploy your application to AWS Lambda and Amazon API Gateway. Follow the steps below:
- Using your terminal, navigate to the application root directory and build the application shaded jar using the command:
- The deployment will require an AWS S3 bucket to store the application code for deployment. So, create an S3 bucket with a unique name:
- Run the following commmand from the project's root folder to copy the application code to the S3 bucket:
- Provide a stack name and run the aws cloudformation deploy command:
- After your application is deployed, you can get the API endpoint that was created by describing the stack:
mvn clean package
aws s3 mb s3://spring-boot-lambda-aws
aws cloudformation package --template-file sam.yaml \
--output-template-file output-sam.yaml --s3-bucket spring-boot-lambda-aws
aws cloudformation deploy --template-file output-sam.yaml \
--stack-name springbootServerlessApi --capabilities CAPABILITY_IAM
Note: Make sure you IAM has capabilities else you may see Access denied error.
aws cloudformation describe-stacks --stack-name springbootServerlessApi \
--query 'Stacks[0].Outputs[*].{Service:OutputKey,Endpoint:OutputValue}'
The output will be something like the following:
Output
[
{
"Service": "SpringbootSampleApi",
"Endpoint": "https://xxxxxx.execute-api.us-east-2.amazonaws.com/Prod/ping"
}
]
You can copy the output value and curl to test your request:
curl -s https://xxxxxx.execute-api.us-east-2.amazonaws.com/Prod/ping | python -m json.tool
{
"pong": "Hello, World!"
}