Lambda and API Gateway with Amazon CDK (Java edition)

by Afanasy Barbarov

The problem: Creating AWS infrastructure with code

One day I realized, that I need to check some ideas using a specific Java library. But from the other side - I don't know Java well. The idea that would help to spend dramatically less time was simple - just to wrap that library in a lambda function and run it when needed.

And in this blog post, I'll tell how to wrap a Java code into an AWS lambda function that runs behind an AWS API Gateway with CORS enabled. So let's try it out.

I will use AWS CDK for the infrastructure provisioning this time. I had some positive experience using it and consider it a good choice for the current problem.

First, you need Java and Maven installed. Homebrew is the best friend for installing software on Mac OS X, so it should be easy to install both. Just a side note - while I was doing it, I had to type brew install twice - mvn command differs from the maven package name, so I did not guess at the first run:).

There is also a great workshop - https://cdkworkshop.com/ to get acquainted with the AWS CDK. I highly recommend spending around an hour and pass it.

So, back to the code:

  1. Create a folder for the lambda and run cdk init sample-app --language java. AWS CDK sample app creates an AWS CloudFormation template for each stack defined. In this case, it defines an SQS queue, an SNS topic, and subscribes that queue to the topic.

  2. Run cdk synth to generate the CloudFormation template.

  3. [Tip] I have different AWS profiles in file ~/.aws/credentials, that looks like

[default]
aws_access_key_id = YOUSHALLNOTPASS
aws_secret_access_key = YOUSHALLNOTPASS

[serverless]
aws_access_key_id = YOUSHALLNOTPASS
aws_secret_access_key = YOUSHALLNOTPASS

To switch to a serverless profile (the one I use to play with AWS infrastructure) I usually run a command export AWS_PROFILE="serverless" in the shell. Maybe there are other 'easy-peasy' approaches, but this works in my case and is fine to me.

  1. The first time you deploy an AWS CDK app into an environment (account/region), you’ll need to install a “bootstrap stack”. This stack includes resources that are needed for the toolkit’s operation. So run cdk bootstrap.

  2. If you check the CloudFormation page in AWS Console, you'll find the new stack.

  3. And we are ready to deploy the stack - first compile the app:

mvn package

and then deploy:

cdk deploy
  1. After that a new stack will be deployed:

image alt stack-update

  1. The cdkworkshop fits ideally to my case - creating a lambda function behind an API Gateway. I just followed the instructions... I removed all the resources described in the LambdaTaggerStack class and tests as well. Don't get me wrong - I like testing, but I don't know Java well and don't want to spend time learning it for this particular problem. So, as I use to say: less code - fewer bugs. After removing the resources - run mvn package again. This time we are able to check the difference with the previous CloudFormation template - run cdk diff, to double-check, that resources are removed. Then run cdk deploy again to clean up resources.

  2. And here I realized, that something is wrong.. The rest of the workshop is about lambdas written in javascript. I did not check before starting the workshop and believed, that everything is about Java - the CDK, the lambda itself, everything!

  3. But I managed to handle that. As it often is - the tutorials work only when you follow them until the end, which was definitely not my case. So after reading some docs and reviewing code, I've decided to adopt a specific sample from the official AWS documentation, found here - https://github.com/aws-samples/aws-cdk-examples/tree/master/java/api-cors-lambda-crud-dynamodb. The idea of the code is simple - you have one app with two modules - cdk and lambda and build them together. and then put the output jar file to the assets and run it as a lambda function. I refactored the code to fit my needs. And of course, won't show, how exactly:) You can check out my github repo https://github.com/abarbarov/lambda-tagger for reference.

  4. But that code was not exactly what I wanted. As you remember, I wanted a lambda and an API Gateway. So I cleaned up all the unnecessary logic. I removed usages of DynamoDB and left only two classes - GetAllItems and GetOneItem.

  5. Feel free to grab the code and replace the current one. Run mvn clean package and cdk diff afterwards and check the resources. You'll get something like this: (all keys are replaced to AAAAA):

[+] AWS::IAM::Role getOneItemFunction/ServiceRole
  getOneItemFunctionServiceRoleAAAAA
[+] AWS::Lambda::Function getOneItemFunction
  getOneItemFunctionAAAAA
[+] AWS::IAM::Role getAllItemsFunction/ServiceRole
  getAllItemsFunctionServiceRoleAAAAA
[+] AWS::Lambda::Function getAllItemsFunction
  getAllItemsFunctionAAAAA
[+] AWS::ApiGateway::RestApi itemsApi
  itemsApiAAAAA
[+] AWS::ApiGateway::Deployment
  itemsApi/Deployment itemsApiDeploymentAAAAA
[+] AWS::ApiGateway::Stage
  itemsApi/DeploymentStage.prod itemsApiDeploymentStageprodAAAAA
[+] AWS::IAM::Role
  itemsApi/CloudWatchRole itemsApiCloudWatchRoleAAAAA
[+] AWS::ApiGateway::Account
  itemsApi/Account itemsApiAccountAAAAA
[+] AWS::ApiGateway::Resource
  itemsApi/Default/items itemsApiitemsAAAAA
[+] AWS::Lambda::Permission
  itemsApi/Default/items/GET/ApiPermission.LambdaTaggerStackitems
  ApiAAAAA.GET..items itemsApiitemsGETApiPermission
  LambdaTaggerStackitemsApiAAAAA
[+] AWS::Lambda::Permission
  itemsApi/Default/items/GET/ApiPermission.Test.Lambda
  TaggerStackitemsApiAAAAA
  .GET..items itemsApiitemsGETApiPermissionTestLambda
  TaggerStackitemsApiAAAAAGETitemsAAAAA
[+] AWS::ApiGateway::Method
  itemsApi/Default/items/GET itemsApiitemsAAAAA
[+] AWS::ApiGateway::Method
  itemsApi/Default/items/OPTIONS itemsApiitemsOPTIONSAAAAA
[+] AWS::ApiGateway::Resource
  itemsApi/Default/items/{id} itemsApiitemsidAAAAA
[+] AWS::Lambda::Permission
  itemsApi/Default/items/{id}/GET/ApiPermission.
  LambdaTaggerStackitemsApiAAAAA.GET..items.{id}
  itemsApiitemsidGETApiPermissionLambdaTaggerStackitems
  ApiAAAAAGETitemsidAAAAA
[+] AWS::Lambda::Permission
  itemsApi/Default/items/{id}/GET/ApiPermission
  .Test.LambdaTaggerStackitemsApiAAAAA.GET..items
  .{id} itemsApiitemsidGETApiPermissionTestLambdaTagger
  StackitemsApiAAAAAGETitemsidAAAAA
[+] AWS::ApiGateway::Method
  itemsApi/Default/items/{id}/GET itemsApiitemsidGETAAAAA
[+] AWS::ApiGateway::Method
  itemsApi/Default/items/{id}/OPTIONS i

which is fine and looks like what I wanted. 11. Run cdk deploy to update the stack. After that note the output. There will be a link to the api gateway, something like

LambdaTaggerStack.itemsApiEndpointAAAAA =
  https://AAAAA.execute-api.eu-west-1.amazonaws.com/prod/
  1. If you try to GET that URL, you'll get
{"message":"Missing Authentication Token"}

That's because AWS returns such message for the wrong URL:) So add items to the end and enjoy.

  1. If you, like me, tried to oversimplify the code, you might get another message - {"message": "Internal server error"}. In that case, you'll need to dig into the CloudWatch logs. For me, the error was in using a wrong jar file for the lambda - lambda-tagger-* instead of lambda-*.

Let's summarize: we have a lambda function that returns something, an API Gateway, and CORS configured. Maybe one day I'll add authorization, but now I'll keep it simple.


That's all, folks!

Written by Afanasy Barbarov — Tech Lead with 15+ years shipping production systems in Rust, Go, and TypeScript. Facing a similar challenge? Reach out on LinkedIn. Support my work.

More articles

Previous post

Send an SMS on a regular basis (poor man's approach).

Read more

Next post

Deploying Python Lambda using SAM and Github actions, covering CI/CD ideas for Terraform.

Read more