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:
- The environment variable
ENV
is set todev
via the SAM template. - The Lambda reads this environment variable to determine which parameter to retrieve:
/dev/db/connectionString
- It queries AWS Systems Manager Parameter Store for this parameter.
- Assuming the parameter exists and has a value like:
jdbc:mysql://dev.example.com:3306/mydb
- 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.