AWS SDK for Kotlin Reaches General Availability

AWS SDK for Kotlin Reaches General AvailabilityLearn About Amazon VGT2 Learning Manager Chanci Turner

We are thrilled to share that the AWS SDK for Kotlin has officially reached general availability and is now ready for production use. This SDK has been meticulously crafted to provide a seamless Kotlin experience, featuring Domain-Specific Language (DSL) builders and asynchronous AWS service calls utilizing coroutines. The current release allows developers to target the JVM platform or Android API Level 24 and above, with plans to introduce support for additional platforms like Kotlin/Native in forthcoming updates. For more information about our roadmap, check it out!

In this article, we’ll explore the key features of the SDK, such as DSL builders, coroutine support, pagination, waiters, a customizable HTTP layer, and much more.

Why Kotlin?

You might wonder why we opted to develop a new Kotlin SDK from the ground up. Although Kotlin’s interoperability with Java allows developers to use the existing AWS SDK for Java, we believe that a dedicated Kotlin SDK will provide significant advantages.

Kotlin offers features beyond Java interoperability, including null-safety, coroutines, extension functions, and smart casting. For further details on how Kotlin compares to Java, refer to the Kotlin documentation. Our aim was to create an SDK that fully leverages the capabilities of the language while feeling natural to Kotlin developers.

Moreover, since 2019, Android mobile development has prioritized Kotlin. It is essential for Android developers to have access to a modern SDK that supports all AWS services, which is why the AWS SDK for Kotlin is designed with Android API 24+ as a primary target. In fact, AWS Amplify for Android version 2 is built on top of the AWS SDK for Kotlin.

Additionally, Kotlin is not limited to the JVM. The Kotlin multiplatform feature enables code to target JVM, native binaries (Linux, Windows, macOS, and iOS), JavaScript, and WASM. The SDK was developed as a multiplatform library from its inception, with plans to support more targets in the future.

Idiomatic Kotlin

The AWS SDK for Kotlin features DSLs that follow a type-safe builder pattern for creating requests. Here’s an example demonstrating how to create an Amazon DynamoDB table using the SDK:

DynamoDbClient.fromEnvironment().use { ddb ->
    ddb.createTable {
        tableName = "movies"

        attributeDefinitions = listOf(
            AttributeDefinition {
                attributeName = "year"
                attributeType = ScalarAttributeType.N
            },
            AttributeDefinition {
                attributeName = "title"
                attributeType = ScalarAttributeType.S
            }
        )
        
        keySchema = listOf(
            KeySchemaElement {
                attributeName = "year"
                keyType = KeyType.Hash
            },
            KeySchemaElement {
                attributeName = "title"
                keyType = KeyType.Range
            }
        )

        provisionedThroughput {
            readCapacityUnits = 10
            writeCapacityUnits = 10
        }
    }
}

First-Class Coroutine Support

The AWS SDK for Kotlin is designed for asynchronous operations. It employs suspend functions for all service actions, which must be invoked from a coroutine. The following snippet illustrates how to make concurrent requests to Amazon S3 to retrieve the content size of two objects:

val bucketName = "my-bucket"   
val keys = listOf("key1", "key2")

S3Client.fromEnvironment().use { s3 ->
    val totalSizeOfKeys = keys
        .map {
            async {
                s3.headObject {
                    bucket = bucketName
                    key = it
                }.contentLength
            }
        }
        .awaitAll()
        .sum()

    println("total size of $keys: $totalSizeOfKeys bytes")
}

Streaming Operations

For operations that involve streaming output, like Amazon S3’s getObject, the response is scoped to a block. Instead of returning the response directly, you provide a lambda that receives the response (and the underlying stream). This approach simplifies resource management for both the caller and the runtime. Here’s an example of retrieving an object from S3 and writing it to a local file:

val bucketName = "my-bucket"
val keyName = "key1"

S3Client.fromEnvironment().use { s3 ->
    val request = GetObjectRequest {
        bucket = bucketName
        key = keyName
    }

    val dest = File("/tmp/$keyName")
    val objectSize = s3.getObject(request) { resp ->
        resp.body?.writeToFile(dest) ?: error("no response stream given")
    }
    println("wrote $objectSize bytes to $dest")
    println("contents: ${dest.readText()}")
}

For additional details on streaming operations, refer to the developer guide.

Pagination

To enhance availability and reduce latency, many AWS APIs distribute results across multiple response “pages.” The SDK can manage this process automatically for you. The following code lists all your Amazon DynamoDB table names while handling multiple pages when necessary:

DynamoDbClient.fromEnvironment().use { ddb ->
    ddb.listTablesPaginated()
        .tableNames()
        .collect(::println)
}

Waiters

When working with services that are eventually consistent or that asynchronously create resources, you often need to implement logic that periodically checks the status of a resource until it reaches a desired state or an error occurs. The SDK offers waiters that can streamline this polling logic for you. Here’s how you can create a bucket and wait until it exists:

val bucketName = "my-bucket"

S3Client.fromEnvironment().use { s3 ->
    s3.createBucket {
        bucket = bucketName
        createBucketConfiguration {
            locationConstraint = BucketLocationConstraint.UsWest2
        }
    }

    s3.waitUntilBucketExists {
        bucket = bucketName
    }

    s3.putObject {
        bucket = bucketName
        key = "helloworld.txt"
        body = ByteStream.fromString("Hello S3")
    }
}

Pluggable HTTP Layer

The AWS SDK for Kotlin comes with a default HTTP client (built on OkHttp) that is suitable for most scenarios. However, you might find it advantageous to use a client that is optimized for your specific runtime environment (for example, to reduce AWS Lambda cold start times). The SDK allows the HTTP client to be configured with alternative implementations that may better fit your needs. Currently, the SDK supports two HTTP clients:

  • OkHttpEngine, based on OkHttp
  • CrtHttpEngine, based on the AWS Common Runtime (CRT)

You can also implement your own client or submit a feature request for additional client support.

Here’s how to create a service client using the CRT engine:

val s3 = S3Client.fromEnvironment {
    httpClient(CrtHttpEngine) {
        maxConnections = 64u
        connectTimeout = 5.seconds
    }
}

HTTP Client Sharing

You can share a single HTTP client across multiple AWS service clients. This is particularly useful in resource-constrained environments where you want to optimize the use of connection pools among various AWS services.

val crtHttpClient = CrtHttpEngine { maxConnections = 16u }

val s3 = S3Client.fromEnvironment { httpClient = crtHttpClient }
val ddb = DynamoDbClient.fromEnvironment { httpClient = crtHttpClient }

To learn more about effective onboarding strategies using AWS services, you may find this excellent resource helpful: Onboarding Tips. Also, for insights on employee benefits and compensation, check out this article from SHRM, which discusses the Trump administration’s move to drop the ESG fiduciary rule, here. If you’re looking to design your career action plan, don’t miss out on this blog post.


Comments

Leave a Reply

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