Deploying Java AWS Lambda to Multiple Environments Using AWS SAM, Parameter Store, and One Codebase

4 min read

Deploying Java AWS Lambda to Multiple Environments Using AWS SAM, Parameter Store, and One Codebase

Managing multiple environments (like dev and prod) with one codebase is a common challenge when building AWS Lambda functions. In this guide, we’ll walk through:

  • Structuring a Java + Maven Lambda project
  • Using AWS SAM templates with parameter overrides
  • Reading environment-specific configuration from Parameter Store
  • Deploying to dev and prod using a single command structure

Best Practices

  • Single Codebase: Use the same JAR for both environments to avoid code drift.
  • Parameter Store: Use SecureString for sensitive data and enable encryption with KMS.
  • IAM Permissions: Grant least privilege access to Parameter Store.
  • CI/CD: Integrate with AWS CodePipeline or GitHub Actions for automated deployments.
  • Versioning: Use Parameter Store versioning to track configuration changes.

Prerequisites

  • AWS CLI configured (aws configure)
  • Java 17+ and Maven installed
  • AWS SAM CLI installed
  • S3 bucket for deployment artifacts
  • IAM permissions to deploy Lambda and access Parameter Store

Project Structure

my-lambda/
├── pom.xml
├── src/
│   └── main/java/
│       └── com/example/
│           └── Handler.java
├── template.yaml
├── samconfig.toml (optional)
└── scripts/
    └── deploy.sh

Step 1: pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>my-lambda</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- AWS Lambda Java Core -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.2.3</version>
        </dependency>

        <!-- AWS SDK v2 for SSM -->
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>ssm</artifactId>
            <version>2.25.10</version>
        </dependency>

        <!-- AWS Lambda Java Events (optional, if you need event models) -->
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-events</artifactId>
            <version>3.11.3</version>
        </dependency>

        <!-- Jackson for JSON handling (optional, if needed) -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

            <!-- Shade Plugin for packaging -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.4.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.example.Handler</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Step 2: Create Your Lambda Function in Java

Here’s a basic Lambda function:

package com.example;

import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.model.GetParameterRequest;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class Handler implements RequestHandler<Object, String> {

    private final SsmClient ssmClient = SsmClient.create();

    @Override
    public String handleRequest(Object input, Context context) {
        String env = System.getenv("ENV");
        String parameterName = String.format("/%s/db/connectionString", env);

        String value = ssmClient.getParameter(GetParameterRequest.builder()
            .name(parameterName)
            .withDecryption(true)
            .build())
            .parameter()
            .value();

        return String.format("Using DB Connection: %s", value);
    }
}

This code reads the ENV environment variable and fetches a Parameter Store value accordingly.


Step 3: SAM Template (template.yaml)

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Java Lambda with Dev/Prod support

Globals:
  Function:
    Timeout: 10
    Runtime: java17
    MemorySize: 512

Parameters:
  Environment:
    Type: String
    AllowedValues:
      - dev
      - prod
    Description: Deployment environment

Resources:
  MyLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub "my-lambda-${Environment}"
      Handler: com.example.Handler::handleRequest
      CodeUri: .
      Environment:
        Variables:
          ENV: !Ref Environment
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /invoke
            Method: get

Step 4: Build the Project

mvn clean package

This will produce a target/my-lambda.jar.


Step 5: Deploy to Dev or Prod

Option A: Use the CLI directly

sam deploy \
  --template-file template.yaml \
  --stack-name my-lambda-dev \
  --capabilities CAPABILITY_IAM \
  --s3-bucket your-deployment-bucket \
  --parameter-overrides Environment=dev
sam deploy \
  --template-file template.yaml \
  --stack-name my-lambda-prod \
  --capabilities CAPABILITY_IAM \
  --s3-bucket your-deployment-bucket \
  --parameter-overrides Environment=prod

Option B: Create a helper script (scripts/deploy.sh)

#!/bin/bash
set -e

ENV=$1
if [[ -z "$ENV" ]]; then
  echo "Usage: ./deploy.sh [dev|prod]"
  exit 1
fi

STACK_NAME="my-lambda-$ENV"
S3_BUCKET="your-deployment-bucket"

sam build

sam deploy \
  --stack-name $STACK_NAME \
  --s3-bucket $S3_BUCKET \
  --capabilities CAPABILITY_IAM \
  --parameter-overrides Environment=$ENV

Make it executable:

chmod +x scripts/deploy.sh
./scripts/deploy.sh dev
./scripts/deploy.sh prod

Step 6: Store Parameters in Parameter Store

Store the environment-specific configuration:

aws ssm put-parameter \
  --name "/dev/db/connectionString" \
  --value "jdbc:mysql://dev.example.com:3306/db" \
  --type "SecureString"

aws ssm put-parameter \
  --name "/prod/db/connectionString" \
  --value "jdbc:mysql://prod.example.com:3306/db" \
  --type "SecureString"

Ensure your Lambda IAM role has permission:

Policies:
  - Version: "2012-10-17"
    Statement:
      - Effect: "Allow"
        Action:
          - "ssm:GetParameter"
        Resource: 
          - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${Environment}/*"

Result if the lambda function is deployed to dev environment

When the Lambda is deployed to the dev environment and invoked, the following happens:

  1. The environment variable ENV is set to dev via the SAM template.
  2. The Lambda reads this environment variable to determine which parameter to retrieve: /dev/db/connectionString
  3. It queries AWS Systems Manager Parameter Store for this parameter.
  4. Assuming the parameter exists and has a value like: jdbc:mysql://dev.example.com:3306/mydb
  5. The Lambda function returns a response similar to:
Using DB Connection: jdbc:mysql://dev.example.com:3306/mydb

If the parameter is missing, the Lambda would throw an exception unless error handling is implemented.

Conclusion

With this setup, you have:

  • One Java codebase
  • One SAM template
  • Configurable environment parameters via Parameter Store
  • Simplified deployments for dev and prod

This approach makes your deployment process cleaner, more secure, and easily scalable.

🤞 Never miss a story from us, get weekly updates to your inbox!

Leave a Reply

Your email address will not be published. Required fields are marked *