Kotlin 2.4.20 allDistinctBy: unique Idempotency-Keys per batch

Kotlin 2.4.20 adds allDistinctBy. Use it to assert one Idempotency-Key per intent before a batch of Sume image submits, then post with java.net.http.

5 min readSume
All posts

Short answer

Before you fire a batch of Sume submits from Kotlin, assert that every item carries a distinct Idempotency-Key. Kotlin 2.4.20, released 7 September 2026, introduced allDistinctBy(), so the guard is one line. The what's new page shows responses.allDistinctBy { it.participantId } as its uniqueness check.

The reason to care: Sume treats a repeated key with the same payload as the same operation and returns the original job, and rejects a repeated key with a different payload with 409 idempotency_conflict. Two different prompts that share a key in one batch would hit the conflict, or worse, one prompt would silently stand in for the other on a retry.

What 2.4.20 adds and what this post uses

Only one new standard-library function is used here, and it is experimental. The coroutine stack-trace recovery interface is also new in 2.4.20, which helps when a poll coroutine rethrows a Sume error, but this post keeps the sample dependency-free.

Kotlin 2.4.20 items from the release notes (read 2026-10-03)
AreaItem
Standard libraryStackTraceRecoverable interface for coroutine exception recovery
Standard libraryallDistinct(), allDistinctBy(), allEqual(), allEqualBy()
Kotlin/NativeSwift export: sealed classes map to Swift enums, plus generated Package.swift
Kotlin/Wasmmulti-module compilation modes: monolith, multimodule-open-world, multimodule-closed-world

The batch submit

Keys are derived from the intent (date, shot name, version), not from a random value at send time. A random key per attempt would defeat retry safety, because the retry would look like a new job and bill again. The guard fails fast before any request leaves the machine. The release notes list these functions as experimental, so the sample opts in with @OptIn(ExperimentalStdlibApi::class).

import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse

data class Shot(val key: String, val prompt: String)

@OptIn(ExperimentalStdlibApi::class)
fun main() {
    val shots = listOf(
        Shot("hero-2026-10-03-a", "Matte black bottle on marble"),
        Shot("hero-2026-10-03-b", "Same bottle, soft window light"),
    )
    check(shots.allDistinctBy { it.key }) { "duplicate Idempotency-Key in batch" }
    val http = HttpClient.newHttpClient()
    for (s in shots) {
        val body = """{"prompt":"${s.prompt}","mode":"async"}"""
        val req = HttpRequest.newBuilder(URI.create("https://api.sume.com/v1/image-1.0/generate"))
            .header("Authorization", "Bearer " + System.getenv("SUME_API_KEY"))
            .header("Content-Type", "application/json")
            .header("Idempotency-Key", s.key)
            .POST(HttpRequest.BodyPublishers.ofString(body)).build()
        val res = http.send(req, HttpResponse.BodyHandlers.ofString())
        println("${res.statusCode()} ${res.body()}")
    }
}

After the submits

Each response carries the job id, status_url and next_poll_after_seconds. Keep the id next to the key so a crash after the POST can be resolved by re-sending the same request with the same key rather than by guessing. The poll loop for the ids is on the Kotlin poll-loop post.

What Sume does and does not do

Sume returns the original job when a retry repeats the key and payload, and it returns 409 idempotency_conflict when the same key arrives with a different payload. Reuse a key only for the same operation and payload.

Sume does not de-duplicate on prompt text. Two submits with different keys and identical prompts are two paid jobs, so the uniqueness check above is about keys that mean the same intent, not about identical prompts.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume