Question: How do I use AWS SAM to build a Lambda-backed custom resource in Java for CloudFormation?

I want to use Java to build an AWS Lambda-backed custom resource to implement in AWS CloudFormation.

Short descriptionBuilding a Java-based custom resource in Lambda for CloudFormation is a complex process, especially if you do this process manually. To set up the environment manually, you need to set up the Java development kit and runtime before you build the resource. Then, you need to package and upload the code to Amazon Simple Storage Service (Amazon S3) before you finally create the Lambda function.To simplify this process, you can use the AWS Serverless Application Model (AWS SAM). AWS SAM is an open-source framework that you can use to build serverless applications on AWS. Use this service to help build the Java custom resource, upload your codes, and deploy the custom resource and Lambda function.

ResolutionCreate an instance for development1.    Create an Amazon Elastic Compute Cloud (Amazon EC2) instance to develop your resource. You can use any environment for your instance, but it's a best practice to create your instance in Amazon Linux 2 for this use case.2.    Create and assign an Amazon EC2 SSH key pair to your EC2 instance.3.    Set up an instance profile with permissions to deploy a stack and resource. Specifically, grant permissions to perform the following actions:Create a Lambda functionUpdate function codeInvoke a functionCreate a log group that stores the Lambda's log4.    After you launch your instance, log in with SSH. Use this instance as your development environment in the following section.Set up your development environmentImportant: Before you begin, install and configure the AWS Command Line Interface (AWS CLI). If you receive errors when running AWS CLI commands, make sure that you’re using the most recent version of the AWS CLI.Install java-corretto11To install corretto11, run the following command:sudo rpm --import https://yum.corretto.aws/corretto.key   sudo curl -L -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo  sudo yum install -y java-11-amazon-corretto-develVerify Installation with the following command:java -versionFor more information, see Install Amazon Corretto 11 on RPM-Based Linux.Install AWS SAMTo install AWS SAM, run the following command:wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zipunzip aws-sam-cli-linux-x86_64.zip -d sam-installationsudo ./sam-installation/installsam --versionFor more information, see Installing the AWS SAM CLI.Install GradleTo install Gradle, run the following command:wget https://services.gradle.org/distributions/gradle-7.6-bin.zipsudo mkdir /opt/gradlesudo unzip -d /opt/gradle gradle-7.6-bin.zipexport PATH=$PATH:/opt/gradle/gradle-7.6/bingradle -vFor more information, see Installing with a package manager on the Gradle website.Note: You might need to export the PATH again after you reboot. To avoid this step, append the following line to the ~/.bashrc file:export PATH=$PATH:/opt/gradle/gradle-7.6/binCreate the project and filesTo create the root folder for the SAM project, run the following command:mkdir javaBasedCustomResourcecd javaBasedCustomResource/To create the necessary folders for your project, run the following command:mkdir -p src/Function/src/main/java/Create the project files through the Vim text editor. For more information, see the Vim website. Copy the following content to run in Vim:vim template.yamlvim ./src/Function/build.gradlevim ./src/Function/src/main/java/Handler.javaYour project's structure resembles the following structural tree:.└── javaBasedCustomResource    ├── src    │   └── Function    │       ├── build.gradle    │       └── src    │           └── main    │               └── java    │                   └── Handler.java    └── template.yamlSee the following sections for the file templates to build your project.template.yamlUse the following template for the stack. This defines the Lambda function, log group, and CustomResource:Transform: AWS::Serverless-2016-10-31Resources:  CustomResourceJavaBased:    Type: AWS::CloudFormation::CustomResource    Properties:      ServiceToken: !Join ["", [ !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:", !Ref CustomResourceLambdaInJava]]      DummyKey: DummyValue  CustomResourceLambdaInJava:    Type: AWS::Serverless::Function    Properties:      Description: !Sub        - Stack ${AWS::StackName} Function ${ResourceName}        - ResourceName: CustomResourceLambdaInJava      CodeUri: src/Function      Handler: Handler::handleRequest      Runtime: java11      MemorySize: 3008      Timeout: 30      Tracing: Active  CustomResourceLambdaInJavaLogGroup:    Type: AWS::Logs::LogGroup    DeletionPolicy: Retain    Properties:      LogGroupName: !Sub /aws/lambda/${CustomResourceLambdaInJava}build.gradleUse the following file template to facilitate your java build:apply plugin: 'java'repositories {    mavenCentral()}dependencies {    implementation 'com.amazonaws:aws-lambda-java-core:1.2.2'    implementation 'com.amazonaws:aws-lambda-java-events:3.11.0'    implementation 'com.google.code.gson:gson:2.10'    }task buildZip(type: Zip) {    from compileJava    from processResources    into('lib') {        from configurations.compileClasspath    }}build.dependsOn buildZipHandler.javaLambda runs the following code. This sets the input event to a Map from which you can get the necessary endpoint URL to send back the response. Put all the needed parameters in a JSON string, and send an HTTP request to the endpoint:import com.google.gson.Gson;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.util.*;import java.io.*;public class Handler {    public String handleRequest(Object event) {    /* Customer workload */    System.out.println("Dummy CustomResource Job");    /* sending signal back to CFN stack */    try {        sendResponse(event);    }    catch (Exception e){        System.out.println("Got Exception!!");        e.printStackTrace(System.out);    }        return "JobFinished";    }        public void sendResponse(Object event) throws IOException, InterruptedException {    System.out.println("start sending signal");        Gson gson = new Gson();    String eventJson = gson.toJson(event);    Map map = gson.fromJson(eventJson, Map.class);        System.out.println("Request event: " + eventJson);    /* Generate response  parameters */    String putEndpoint = (String)map.get("ResponseURL");    String Status = "SUCCESS"; // "SUCCESS" or "FAILED"        String Reason = "Dummy reason for Java based Custom Resource";    String PhysicalResourceId = "CustomResourcePhysicalID";    String StackId = (String)map.get("StackId");    String RequestId = (String)map.get("RequestId");    String LogicalResourceId = (String)map.get("LogicalResourceId");    /* Building response */        String responseJson = "{\"Status\":\"" + Status + "\",\"Reason\":\"" + Reason + "\",\"PhysicalResourceId\":\"" + PhysicalResourceId + "\",\"StackId\":\"" + StackId + "\",\"RequestId\":\"" + RequestId + "\",\"LogicalResourceId\":\"" + LogicalResourceId + "\",\"NoEcho\":false,\"Data\":{\"Key\":\"Value\"}}";    System.out.println("Response event: " + responseJson);    var request = HttpRequest.newBuilder()            .uri(URI.create(putEndpoint))            .header("Content-Type", "application/json")            .PUT(HttpRequest.BodyPublishers.ofString(responseJson))            .build();        var client = HttpClient.newHttpClient();        /* Sending Response */    System.out.println("Sending Response to stack, response code: ");        var response = client.send(request, HttpResponse.BodyHandlers.ofString());        System.out.println(response.statusCode());        System.out.println(response.body());    System.out.println("Finish sending signal");    }}Deploy the project1.    After you create your AWS SAM project, run following command under the root project folder javaBasedCustomResource:sam build2.    To deploy the project, run the following command. This initiates a guide that prompts you to specify the the stack name and AWS Region where you want the resources to be created:sam deploy --guidedThis deploys a CloudFormation stack in your account with the name that you specified. The stack contains a Lambda function that runs the code from the previous steps. CloudFormation also creates the custom resource type AWS::CloudFormation::CustomResource in the same stack.When CloudFormation creates AWS::CloudFormation::CustomResource, CloudFormation also invokes the above Lambda function. In response, the Lambda function sends a SUCCESS signal back to CloudFormation. This SUCCESS signal sends the resource to CreateComplete.When you see the custom resource in the stack create successfully, this means that Lambda is also running successfully. Lambda returns the signal to the stack, and the java based custom resource initiates successfully.You can check the Lambda logs for more details on metrics such as the request event and response event. You can modify the function code to specify your own tasks or create your own resources.