Kotlin Native Runtime for AWS Lambda

A runtime for executing AWS Lambda Functions written in Kotlin/Native, designed to reduce cold start issues common with the JVM platform.
Project Structure
lambda-runtime: Library providing the Lambda runtime.
lambda-events: Library with strongly-typed Lambda event models like APIGatewayRequest, DynamoDBEvent, S3Event, KafkaEvent, SQSEvent, etc.
sample: Sample project demonstrating examples of lambda functions.
- Amazon Linux 2023 (provided.al2023) with x86_64 or arm64 architecture
- Amazon Linux 2 (provided.al2) with x86_64 or arm64 architecture
Performance
Benchmarks of Kotlin Native's "Hello World" Lambda function on Amazon Linux 2023 (x86_64) configured with different amounts of provisioned memory. For more detailed benchmarks, read the article.
- Lambda 128mb provisioned memory. Kotlin/Native outperforms JVM-based runtimes like GraalVM and Java 17 and Java 21 SnapStart.

- Lambda 256mb provisioned memory. Competitive with Python in execution time and memory usage, while surpassing JVM-based runtimes.
Getting started
1. Create a Kotlin Multiplatform Project
See Get started with Kotlin/Native for more details.
2. Add Dependencies
Add the following to your build.gradle file:
plugins {
id("io.github.trueangle.plugin.lambda") version "0.0.2"
}
kotlin {
sourceSets {
nativeMain.dependencies {
implementation("io.github.trueangle:lambda-runtime:$lambda_runtime_version")
implementation("io.github.trueangle:lambda-events:$lambda_runtime_version")
}
}
}
buildLambdaRelease {
architecture.set(Architecture.LINUX_X64)
}
3. Specify Entry Point and Targets
kotlin {
listOf(
macosArm64(),
macosX64(),
linuxX64(),
).forEach {
it.binaries {
executable {
entryPoint = "com.github.trueangle.knative.lambda.runtime.sample.main"
freeCompilerArgs += listOf("-Xallocator=mimalloc")
}
}
}
}
4. Choose Lambda Function Type
Buffered
Buffered Lambda functions collect all data before sending a response. This is a default behavior of Lambda function. Response payload max size: 6 MB.
class HelloWorldLambdaHandler : LambdaBufferedHandler<APIGatewayV2Request, APIGatewayV2Response> {
override suspend fun handleRequest(
input: APIGatewayV2Request,
context: Context
): APIGatewayV2Response {
APIGatewayV2Response(
statusCode = ,
body = ,
cookies = ,
headers = ,
isBase64Encoded =
)
}
}
LambdaBufferedHandler<I, O> is designed to work with any Kotlin class that is supported by the Kotlin serialization library as both the input and output types. This allows you to define your own custom request and response models annotated with @Serializable or utilize existing ones provided by the lambda-events module.
Streaming
A streaming function sends data as soon as it's available, instead of waiting for all the data. It processes and returns the response in chunks, which is useful for large or ongoing tasks. This allows for faster responses and can handle data as it comes in. More details here. For example, SampleStreamingHandler reads a large json file and streams it in chunks.
class SampleStreamingHandler : LambdaStreamHandler<ByteArray, ByteWriteChannel> {
override suspend fun handleRequest(
input: ByteArray,
output: ByteWriteChannel,
context: Context
) {
ByteReadChannel(SystemFileSystem.source(Path("hello.json")).buffered()).copyTo(output)
}
}
LambdaStreamHandler<I, ByteWriteChannel> accepts any serializable input and outputs to ByteWriteChannel which is then streamed to the client.
Both LambdaBufferedHandler and LambdaStreamHandler are suspend functions, so you can use Kotlin coroutines to handle the request in non-blocking way.
Each handler accepts Context object which can be used to get information about the function execution environment, such as the request ID, resource limits, and other details.
5. Specify Main Function
Create application entry point using standard main function. Call LambdaRuntime.run to execute Lambda by passing handler to it.
fun main() = LambdaRuntime.run { HelloWorldLambdaHandler() }
Or for SampleStreamingHandler
fun main() = LambdaRuntime.run { SampleStreamingHandler() }
For more examples refer to project's sample.
Testing Runtime locally
Use the AWS runtime emulator to run the runtime locally.
./gradlew build to build the Lambda executable.
- Modify
runtime-emulator/Dockerfile to set the path to the generated executable (.kexe) file in build/bin/linuxX64/releaseExecutable.
- Run
docker build -t sample:latest .
Build and deploy to AWS
- Make sure you have applied the plugin
id("io.github.trueangle.plugin.lambda") version "0.0.1"
- Execute
./gradlew buildLambdaRelease. The command will output the path to the archive containing lambda executable (YOUR_MODULE_NAME.kexe) located in (YOUR_MODULE_NAME/build/bin/lambda/release/YOUR_MODULE_NAME.zip)
- Deploy .zip archive to AWS. If you have never used AWS Lambda
before, learn how to deploy Lambda function as zip archive manually
or
using AWS CLI:
$ aws lambda create-function --function-name LAMBDA_FUNCTION_NAME \
--handler YOUR_MODULE_NAME.kexe \
--zip-file YOUR_MODULE_NAME.zip \
--runtime provided.al2023 \
--architecture x86_64 \
--role arn:aws:iam::XXXXXXXXXXXXX:role/YOUR_LAMBDA_EXECUTION_ROLE \
--tracing-config Mode=Active
Test the function using the AWS CLI:
$ aws lambda invoke
--cli-binary-format raw-in-base64-out \
--function-name LAMBDA_FUNCTION_NAME \
--payload '{"command": "Say Hi!"}' \
output.json
$ cat output.json
Logging
The Runtime uses AWS logging conventions for enhanced log capture, supporting String and JSON log
output
format. It also allows to dynamically control log levels without altering your code, simplifying
the debugging process. Additionally, you can direct logs to specific Amazon CloudWatch log groups,
making log management and aggregation more efficient at scale. More details on how to set log format
and level refer to the article.
https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/
Use the global Log object with extension functions. The log message accepts any object / primitive type.
Log.trace(message: T?)
Log.debug(message: T?)
Log.info(message: T?)
Log.warn(message: T?)
Log.error(message: T?)
Log.fatal(message: T?)
Troubleshooting
- For Amazon Linux 2023, create a Lambda layer with the libcrypt.so dependency. This library can be taken from your Linux machine or via the Github Action workflow.
- Currently, it's not possible to build ARM type target on Linux machine (use macOS instead) as Kotlin repo misses necessary metadata. For more details, see: