CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

82

Quality

82%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Validation failed for skills in this tile
One or more skills have errors that need to be fixed before they can move to Implementation and Discovery review.
Overview
Quality
Evals
Security
Files

raw-java-lambda.mdplugins/developer-kit-java/skills/aws-lambda-java-integration/references/

Raw Java Lambda Reference

Complete guide for creating minimal AWS Lambda functions in pure Java without frameworks.

Table of Contents

  1. Project Structure
  2. Minimal Handler
  3. Singleton Pattern
  4. JAR Packaging
  5. Performance Tuning

Project Structure

Gradle Configuration

plugins {
    id 'java'
    id 'com.github.johnrengelman.shadow' version '8.1.1'
}

group = 'com.example'
version = '1.0.0'

java {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
}

repositories {
    mavenCentral()
}

dependencies {
    // AWS Lambda Core
    implementation 'com.amazonaws:aws-lambda-java-core:1.2.3'
    implementation 'com.amazonaws:aws-lambda-java-events:3.11.4'
    implementation 'com.amazonaws:aws-lambda-java-log4j2:1.5.1'

    // JSON Processing (choose one)
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0'
    // Or use org.json for minimal size
    // implementation 'org.json:json:20231013'

    // AWS SDK (optional - include only what you need)
    implementation 'software.amazon.awssdk:dynamodb:2.21.0'

    // Testing
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
    testImplementation 'org.mockito:mockito-core:5.7.0'
}

test {
    useJUnitPlatform()
}

shadowJar {
    archiveClassifier = ''
    mergeServiceFiles()
}

Maven Configuration

<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>raw-java-lambda</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-events</artifactId>
            <version>3.11.4</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.16.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Directory Structure

raw-java-lambda/
├── build.gradle (or pom.xml)
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/
│   │   │       ├── Handler.java
│   │   │       ├── service/
│   │   │       │   ├── UserService.java
│   │   │       │   └── UserServiceImpl.java
│   │   │       ├── model/
│   │   │       │   └── User.java
│   │   │       └── util/
│   │   │           └── JsonUtil.java
│   │   └── resources/
│   │       └── log4j2.xml
│   └── test/
│       └── java/
│           └── com/example/
│               └── HandlerTest.java
└── serverless.yml (or template.yaml)

Minimal Handler

Basic Request Handler

package com.example;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.example.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Map;

public class Handler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    // Static initialization for reuse across invocations
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final UserService userService = new UserServiceImpl();

    @Override
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent request, Context context) {
        try {
            context.getLogger().log("Processing request: " + request.getHttpMethod() + " " + request.getPath());

            String method = request.getHttpMethod();
            String path = request.getPath();

            return switch (method) {
                case "GET" -> handleGet(request, context);
                case "POST" -> handlePost(request, context);
                case "PUT" -> handlePut(request, context);
                case "DELETE" -> handleDelete(request, context);
                default -> createResponse(405, "{\"error\": \"Method not allowed\"}");
            };

        } catch (Exception e) {
            context.getLogger().log("Error: " + e.getMessage());
            return createResponse(500, "{\"error\": \"Internal server error\"}");
        }
    }

    private APIGatewayProxyResponseEvent handleGet(APIGatewayProxyRequestEvent request, Context context) {
        String userId = request.getPathParameters().get("id");
        User user = userService.findById(userId);

        if (user == null) {
            return createResponse(404, "{\"error\": \"User not found\"}");
        }

        try {
            String body = mapper.writeValueAsString(user);
            return createResponse(200, body);
        } catch (Exception e) {
            return createResponse(500, "{\"error\": \"Serialization error\"}");
        }
    }

    private APIGatewayProxyResponseEvent handlePost(APIGatewayProxyRequestEvent request, Context context) {
        try {
            User user = mapper.readValue(request.getBody(), User.class);
            User created = userService.create(user);
            String body = mapper.writeValueAsString(created);
            return createResponse(201, body);
        } catch (Exception e) {
            return createResponse(400, "{\"error\": \"Invalid request body\"}");
        }
    }

    private APIGatewayProxyResponseEvent handlePut(APIGatewayProxyRequestEvent request, Context context) {
        // Implementation
        return createResponse(200, "{\"message\": \"Updated\"}");
    }

    private APIGatewayProxyResponseEvent handleDelete(APIGatewayProxyRequestEvent request, Context context) {
        String userId = request.getPathParameters().get("id");
        userService.delete(userId);
        return createResponse(204, "");
    }

    private APIGatewayProxyResponseEvent createResponse(int statusCode, String body) {
        return new APIGatewayProxyResponseEvent()
            .withStatusCode(statusCode)
            .withBody(body)
            .withHeaders(Map.of(
                "Content-Type", "application/json",
                "Access-Control-Allow-Origin", "*"
            ));
    }
}

Stream Handler (Binary Data)

package com.example;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.*;
import java.nio.charset.StandardCharsets;

public class StreamHandler implements RequestStreamHandler {

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        try {
            // Parse input
            JsonNode root = mapper.readTree(inputStream);
            String action = root.path("action").asText();

            // Process based on action
            String result = switch (action) {
                case "process" -> processData(root);
                case "transform" -> transformData(root);
                default -> "{\"error\": \"Unknown action\"}";
            };

            // Write output
            outputStream.write(result.getBytes(StandardCharsets.UTF_8));

        } catch (Exception e) {
            String error = "{\"error\": \"" + e.getMessage() + "\"}";
            outputStream.write(error.getBytes(StandardCharsets.UTF_8));
        }
    }

    private String processData(JsonNode input) {
        // Processing logic
        return "{\"status\": \"processed\"}";
    }

    private String transformData(JsonNode input) {
        // Transformation logic
        return "{\"status\": \"transformed\"}";
    }
}

S3 Event Handler

package com.example;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification;

public class S3Handler implements RequestHandler<S3Event, String> {

    @Override
    public String handleRequest(S3Event event, Context context) {
        for (S3EventNotification.S3EventNotificationRecord record : event.getRecords()) {
            String bucket = record.getS3().getBucket().getName();
            String key = record.getS3().getObject().getKey();

            context.getLogger().log("Processing s3://" + bucket + "/" + key);

            // Process the S3 object
            processS3Object(bucket, key, context);
        }
        return "Processed " + event.getRecords().size() + " records";
    }

    private void processS3Object(String bucket, String key, Context context) {
        // Implementation
    }
}

Singleton Pattern

Application Context Cache

package com.example;

import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.s3.S3Client;

/**
 * Application context singleton for caching initialized services.
 * Initialized once on first Lambda container creation, reused for warm invocations.
 */
public final class ApplicationContext {

    // Singleton instance
    private static volatile ApplicationContext instance;
    private static final Object lock = new Object();

    // Cached clients
    private final DynamoDbClient dynamoDbClient;
    private final S3Client s3Client;
    private final String usersTableName;

    private ApplicationContext() {
        // Initialize AWS clients
        this.dynamoDbClient = DynamoDbClient.create();
        this.s3Client = S3Client.create();

        // Load configuration from environment
        this.usersTableName = System.getenv("USERS_TABLE");
        if (usersTableName == null) {
            throw new IllegalStateException("USERS_TABLE environment variable required");
        }

        // Pre-warm connections if needed
        validateConnections();
    }

    public static ApplicationContext getInstance() {
        if (instance == null) {
            synchronized (lock) {
                if (instance == null) {
                    instance = new ApplicationContext();
                }
            }
        }
        return instance;
    }

    private void validateConnections() {
        // Optional: Verify connections work
        dynamoDbClient.listTables();
    }

    public DynamoDbClient getDynamoDbClient() {
        return dynamoDbClient;
    }

    public S3Client getS3Client() {
        return s3Client;
    }

    public String getUsersTableName() {
        return usersTableName;
    }
}

Lazy Initialization Service

package com.example.service;

import com.example.ApplicationContext;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;

public class DynamoDbUserService implements UserService {

    // Lazy-loaded via singleton
    private final DynamoDbClient dynamoDb;
    private final String tableName;

    public DynamoDbUserService() {
        ApplicationContext ctx = ApplicationContext.getInstance();
        this.dynamoDb = ctx.getDynamoDbClient();
        this.tableName = ctx.getUsersTableName();
    }

    @Override
    public User findById(String id) {
        GetItemRequest request = GetItemRequest.builder()
            .tableName(tableName)
            .key(Map.of("id", AttributeValue.builder().s(id).build()))
            .build();

        GetItemResponse response = dynamoDb.getItem(request);

        if (!response.hasItem()) {
            return null;
        }

        return mapToUser(response.item());
    }

    @Override
    public User create(User user) {
        PutItemRequest request = PutItemRequest.builder()
            .tableName(tableName)
            .item(mapFromUser(user))
            .conditionExpression("attribute_not_exists(id)")
            .build();

        dynamoDb.putItem(request);
        return user;
    }

    @Override
    public void delete(String id) {
        DeleteItemRequest request = DeleteItemRequest.builder()
            .tableName(tableName)
            .key(Map.of("id", AttributeValue.builder().s(id).build()))
            .build();

        dynamoDb.deleteItem(request);
    }

    private User mapToUser(Map<String, AttributeValue> item) {
        User user = new User();
        user.setId(item.get("id").s());
        user.setName(item.get("name").s());
        user.setEmail(item.get("email").s());
        return user;
    }

    private Map<String, AttributeValue> mapFromUser(User user) {
        Map<String, AttributeValue> item = new HashMap<>();
        item.put("id", AttributeValue.builder().s(user.getId()).build());
        item.put("name", AttributeValue.builder().s(user.getName()).build());
        item.put("email", AttributeValue.builder().s(user.getEmail()).build());
        return item;
    }
}

Handler with Context

package com.example;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.example.service.DynamoDbUserService;
import com.example.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Handler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    // Static initialization - runs once per container
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final UserService userService = new DynamoDbUserService();

    @Override
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent request, Context context) {
        // Log initialization timing
        long startTime = System.currentTimeMillis();

        try {
            // Process request using cached services
            String userId = request.getPathParameters().get("id");
            User user = userService.findById(userId);

            long duration = System.currentTimeMillis() - startTime;
            context.getLogger().log("Request processed in " + duration + "ms");

            return new APIGatewayProxyResponseEvent()
                .withStatusCode(200)
                .withBody(mapper.writeValueAsString(user));

        } catch (Exception e) {
            context.getLogger().log("Error: " + e.getMessage());
            return new APIGatewayProxyResponseEvent()
                .withStatusCode(500)
                .withBody("{\"error\": \"Internal error\"}");
        }
    }
}

JAR Packaging

Minimal JAR Configuration

// build.gradle
shadowJar {
    archiveClassifier = ''
    mergeServiceFiles()

    // Exclude unnecessary files to reduce size
    exclude 'META-INF/*.SF'
    exclude 'META-INF/*.DSA'
    exclude 'META-INF/*.RSA'
    exclude 'META-INF/LICENSE*'
    exclude 'META-INF/NOTICE*'
    exclude 'META-INF/DEPENDENCIES'

    // Minimize to remove unused dependencies
    minimize {
        // Keep these packages even if not directly referenced
        exclude(dependency('com.amazonaws:.*'))
        exclude(dependency('software.amazon.awssdk:.*'))
        exclude(dependency('org.apache.logging.log4j:.*'))
    }
}

Custom Packaging Script

// build.gradle - custom tasks
tasks.register('packageForLambda', Zip) {
    from(shadowJar.outputs)
    archiveFileName = "function.zip"
    destinationDirectory = layout.buildDirectory.dir('distributions')
}

tasks.register('deployLocal', Copy) {
    dependsOn shadowJar
    from shadowJar.outputs
    into layout.buildDirectory.dir('sam-local')
}

Maven Shade Configuration

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <createDependencyReducedPom>false</createDependencyReducedPom>
        <filters>
            <filter>
                <artifact>*:*</artifact>
                <excludes>
                    <exclude>META-INF/*.SF</exclude>
                    <exclude>META-INF/*.DSA</exclude>
                    <exclude>META-INF/*.RSA</exclude>
                    <exclude>META-INF/LICENSE*</exclude>
                    <exclude>META-INF/NOTICE*</exclude>
                </excludes>
            </filter>
        </filters>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

Performance Tuning

JVM Options for Lambda

# serverless.yml
provider:
  name: aws
  runtime: java21
  environment:
    # Optimize for short-lived Lambda
    JAVA_TOOL_OPTIONS: >-
      -XX:+TieredCompilation
      -XX:TieredStopAtLevel=1
      -XX:+UseSerialGC
      -Xmx256m
      -XX:MaxMetaspaceSize=128m

Custom Runtime (Advanced)

For maximum cold start reduction, consider GraalVM native image:

# Dockerfile for custom runtime
FROM ghcr.io/graalvm/graalvm-ce:ol9-java21-21.0.2 AS builder
WORKDIR /app
COPY . .
RUN ./gradlew nativeCompile

FROM public.ecr.aws/lambda/provided:al2023
COPY --from=builder /app/build/native/nativeCompile/my-app ${LAMBDA_RUNTIME_DIR}/bootstrap
CMD ["com.example.Handler"]

Memory Configuration Guide

JAR SizeMin MemoryRecommendedNotes
< 10MB256MB512MBSimple handlers
10-50MB512MB1024MBWith AWS SDK
> 50MB1024MB1769MBLarge dependencies

Cold Start Benchmarks

Typical cold start times for Raw Java (512MB):

  • Minimal handler (no AWS SDK): ~150-250ms
  • With DynamoDB client: ~200-350ms
  • With multiple clients: ~300-500ms

Warm start times: typically < 10ms


Logging Configuration

<!-- src/main/resources/log4j2.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <Lambda name="Lambda">
            <PatternLayout>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1} - %m%n</pattern>
            </PatternLayout>
        </Lambda>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Lambda"/>
        </Root>
        <Logger name="software.amazon.awssdk" level="warn"/>
    </Loggers>
</Configuration>

Best Practices Summary

  1. Always use static fields for service initialization
  2. Keep JAR size minimal - exclude unused dependencies
  3. Use Java 21 for best performance
  4. Set appropriate memory - start with 512MB
  5. Handle exceptions gracefully with proper HTTP status codes
  6. Log request IDs for troubleshooting
  7. Validate environment variables at startup
  8. Reuse AWS clients - don't create per-request

plugins

developer-kit-java

skills

README.md

tile.json