The ultimate beginners guide to Java Serverless

The Serverless framework has become very popular because it's easy to set up and use and it makes the whole AWS Serverless deploying process much easier. It also allows you to test some of AWS features before deployment. This tutorial will guide you through the creation of one simple REST service with AWS Lambda behind the curtains, its testing and its deployment to AWS. We'll also cover the set up of basic dependencies needed for serverless to work. 

Prerequisites

You should have Java JDK, Maven and Serverless framework installed. If you have all that in place, skip to the project set up.

1. Java - To check if there's Java on your system type java -version on command prompt. If you have it but this command says that command java is not found, that probably means that Java is not added to Path environment variable. 

C:\Users\Meca>java -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) Client VM (build 25.231-b11, mixed mode)

2. Maven - Download Maven from this location. Add it's bin folder to your path. For me, it's C:\Program Files\Maven\apache-maven-3.6.3\bin. With maven -v check if mvn command works on console.

C:\Users\Meca>mvn -v
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: C:\Program Files\Maven\apache-maven-3.6.3\bin\..
Java version: 1.8.0_181, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk1.8.0_181\jre
Default locale: en_US, platform encoding: Cp1252
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

To install the serverless framework, you'll need npm (that means that you'll need to have NodeJS installed on your system since npm is part of it). If you don't have NodeJS, download and install it.

Then type:

npm install -g serverless

After you installed serverless, type serverless -v to check if everything is in place on your system.

C:\Users\Meca>serverless -v
Framework Core: 1.66.0
Plugin: 3.5.0
SDK: 2.3.0
Components: 2.22.3

Project set up

Type:

serverless create --template aws-java-maven --name get-text-api -p aws-java-get-text-api

This command will create a project for you based on aws-java-maven boilerplate. 

--name parameter value represents the name of your future API.

-p parameter value represents your project name

C:\Users\Meca\Documents\Projects\javahowtos>serverless create --template aws-java-maven --name get-text-api -p aws-java-get-text-api
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "C:\Users\Meca\Documents\Projects\javahowtos\aws-java-get-text-api"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.66.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-java-maven"

You'll see the location of your newly created project on the console and it will have the following structure:

The ultimate beginners guide to Java Serverless

Project code is available on gitHub

Pom.xml file will have basic dependencies required for lambda function development and you'll see following in project definition tags (we changed the default "hello" to "get-text-api"):

<groupId>com.serverless</groupId>
<artifactId>get-text-api</artifactId>
<packaging>jar</packaging>
<version>dev</version>
<name>get-text-api</name>

Serverless.yml file contains the definitions of the Lambda functions, events that trigger them and also definition of resources needed for your functions to be created on AWS.

Be sure that you have one function called getText, since it will represent your endpoint:

package:
  artifact: 'target/${self:service}-${self:provider.stage}.jar'

functions:
  getText:
    handler: com.serverless.Handler

In Handler class we'll set "Some response message." as String to be returned by our service. Also, we'll log "Your first log message.".

package com.serverless;

import java.util.Collections;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class Handler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {

	private static final Logger LOG = LogManager.getLogger(Handler.class);

	@Override
	public ApiGatewayResponse handleRequest(Map<String, Object> input, Context context) {
		LOG.info("Your first log message.", input);
		Response responseBody = new Response("Some response message.", input);
		return ApiGatewayResponse.builder().setStatusCode(200).setObjectBody(responseBody).build();
	}
}

Define your function in serverless.yml file. Notice that we explicitly named region in serverless.yml file since if region value is not provided your lambda will be deployed in default region which is us-east-1 (N. Virginia).

service: get-text-api
provider:
  name: aws
  runtime: java8
  stage: dev
  region: eu-west-1
package:
  artifact: 'target/${self:service}-${self:provider.stage}.jar'
functions:
  getText:
    handler: com.serverless.Handler
    events:
      - http:
          path: api/gettext
          method: get

Testing

For local testing of your Lambda function, you should use sls invoke local --function yourFunctionName command. So, for this case it would be: 

C:\Users\Meca\Documents\Projects\javahowtos\aws-java-get-text-api>sls invoke local --function getText

2020-03-25 23:50:58  INFO  Handler:18 - Your first log message.
{"message":"Some response message.","input":{}}

Important:

In case you get java.lang.NoSuchMethodError: com.amazonaws.services.lambda.runtime.LambdaLogger.log when you execute your function locally, it means that the boilerplate project contains an inadequate version of log4j2 library. To solve this, replace the version of log4j2 artifact in the pom.xml file to 1.0.0.

What you'll see is your response and also you'll see that the log message we defined is logged on the console. 

Deployment

We recommend having one AWS user that will only be able to access AWS programmatically, not through the console. Also, be sure that this account is given only the necessary privileges.

C:\Users\Meca\Documents\Projects\javahowtos\aws-java-get-text-api>sls deploy
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service get-text-api-dev.jar file to S3 (3.22 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................
Serverless: Stack update finished...
Service Information
service: get-text-api
stage: dev
region: eu-west-1
stack: get-text-api-dev
resources: 12
api keys:
  None
endpoints:
  GET - https://0xjvwlagz0.execute-api.us-east-1.amazonaws.com/dev/api/gettext
functions:
  getText: get-text-api-dev-getText
layers:
  None
Serverless: Removing old service artifacts from S3...
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

When you log in to your AWS console, you'll be able to see your Lambda function, API Gateway definition under dev stage and also, you can find the log message we defined on CloudWatch. 

aws console api gateway

aws console cloudwatch

Project code is available on gitHub