Claude-skill-registry lang-kotlin-library-dev

Kotlin-specific library development patterns. Use when creating Kotlin libraries, designing idiomatic Kotlin APIs with extension functions and DSLs, configuring Gradle Kotlin DSL (build.gradle.kts), managing multiplatform projects, testing with Kotest/JUnit, writing KDoc documentation, or publishing to Maven Central. Extends meta-library-dev with Kotlin tooling and ecosystem practices.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/lang-kotlin-library-dev" ~/.claude/skills/majiayu000-claude-skill-registry-lang-kotlin-library-dev && rm -rf "$T"
manifest: skills/data/lang-kotlin-library-dev/SKILL.md
source content

Kotlin Library Development

Kotlin-specific patterns for library development. This skill extends

meta-library-dev
with Kotlin tooling, multiplatform capabilities, and ecosystem practices.

This Skill Extends

  • meta-library-dev
    - Foundational library patterns (API design, versioning, testing strategies)

For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first.

This Skill Adds

  • Kotlin tooling: Gradle Kotlin DSL, multiplatform configuration, compiler plugins
  • Kotlin idioms: Extension functions, DSL design, sealed classes, inline functions
  • Kotlin ecosystem: Maven Central publishing, KDoc, Dokka, multiplatform targets

This Skill Does NOT Cover

  • General library patterns - see
    meta-library-dev
  • Android-specific library development - see Android skills
  • Kotlin/JS frontend development - see frontend skills
  • General Kotlin syntax - see
    lang-kotlin-dev

Quick Reference

TaskCommand/Pattern
New library
gradle init --type kotlin-library
Build
./gradlew build
Test
./gradlew test
Generate docs
./gradlew dokkaHtml
Publish (local)
./gradlew publishToMavenLocal
Publish (Maven Central)
./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
Check API compatibility
./gradlew apiCheck
(requires binary-compatibility-validator)

Gradle Kotlin DSL Structure

build.gradle.kts (JVM Library)

plugins {
    kotlin("jvm") version "1.9.22"
    id("org.jetbrains.dokka") version "1.9.20"
    `maven-publish`
    signing
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

kotlin {
    jvmToolchain(17)

    compilerOptions {
        freeCompilerArgs.add("-Xjsr305=strict")
        allWarningsAsErrors.set(true)
    }
}

dependencies {
    implementation(kotlin("stdlib"))

    testImplementation(kotlin("test"))
    testImplementation("io.kotest:kotest-runner-junit5:5.8.0")
    testImplementation("io.kotest:kotest-assertions-core:5.8.0")
}

tasks.test {
    useJUnitPlatform()
}

java {
    withJavadocJar()
    withSourcesJar()
}

publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])

            pom {
                name.set("My Kotlin Library")
                description.set("A brief description of what this library does")
                url.set("https://github.com/username/repo")

                licenses {
                    license {
                        name.set("The Apache License, Version 2.0")
                        url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
                    }
                }

                developers {
                    developer {
                        id.set("username")
                        name.set("Your Name")
                        email.set("email@example.com")
                    }
                }

                scm {
                    connection.set("scm:git:git://github.com/username/repo.git")
                    developerConnection.set("scm:git:ssh://github.com/username/repo.git")
                    url.set("https://github.com/username/repo")
                }
            }
        }
    }

    repositories {
        maven {
            name = "sonatype"
            url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials {
                username = project.findProperty("sonatypeUsername") as String?
                password = project.findProperty("sonatypePassword") as String?
            }
        }
    }
}

signing {
    sign(publishing.publications["maven"])
}

gradle.properties

# Publishing configuration
signing.keyId=ABCD1234
signing.password=your-gpg-password
signing.secretKeyRingFile=/path/to/secring.gpg

sonatypeUsername=your-username
sonatypePassword=your-password

# Kotlin configuration
kotlin.code.style=official
kotlin.parallel.tasks.in.project=true

# Build optimizations
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
org.gradle.parallel=true
org.gradle.caching=true

Multiplatform Configuration

build.gradle.kts (Multiplatform)

plugins {
    kotlin("multiplatform") version "1.9.22"
    id("org.jetbrains.dokka") version "1.9.20"
    `maven-publish`
}

group = "com.example"
version = "1.0.0"

kotlin {
    // JVM target
    jvm {
        compilations.all {
            kotlinOptions.jvmTarget = "17"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
    }

    // JavaScript target
    js(IR) {
        browser()
        nodejs()
    }

    // Native targets
    linuxX64()
    macosX64()
    macosArm64()
    mingwX64()

    // iOS targets
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    sourceSets {
        // Common source set
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
            }
        }

        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }

        // JVM-specific
        val jvmMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.7.3")
            }
        }

        val jvmTest by getting

        // JS-specific
        val jsMain by getting
        val jsTest by getting

        // Native shared
        val nativeMain by creating {
            dependsOn(commonMain)
        }

        val nativeTest by creating {
            dependsOn(commonTest)
        }

        // Configure all native targets
        val linuxX64Main by getting { dependsOn(nativeMain) }
        val macosX64Main by getting { dependsOn(nativeMain) }
        val macosArm64Main by getting { dependsOn(nativeMain) }
        val mingwX64Main by getting { dependsOn(nativeMain) }

        val iosX64Main by getting { dependsOn(nativeMain) }
        val iosArm64Main by getting { dependsOn(nativeMain) }
        val iosSimulatorArm64Main by getting { dependsOn(nativeMain) }
    }
}

Hierarchical Source Set Structure

src/
├── commonMain/kotlin/
│   └── com/example/
│       └── Library.kt
├── commonTest/kotlin/
│   └── com/example/
│       └── LibraryTest.kt
├── jvmMain/kotlin/
│   └── com/example/
│       └── JvmSpecific.kt
├── jvmTest/kotlin/
├── jsMain/kotlin/
├── jsTest/kotlin/
├── nativeMain/kotlin/
│   └── com/example/
│       └── NativeSpecific.kt
└── nativeTest/kotlin/

Idiomatic Kotlin API Design

Extension Functions

Use extension functions for fluent APIs:

// Good: Extension function pattern
fun String.toSnakeCase(): String =
    this.replace(Regex("([a-z])([A-Z])"), "$1_$2").lowercase()

// Usage
val result = "HelloWorld".toSnakeCase() // "hello_world"

// Good: Extension on generic types
fun <T> List<T>.second(): T? = this.getOrNull(1)

// Good: Extension with receiver
inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

DSL Design

Type-safe builders for configuration:

// DSL API
class HttpClient internal constructor() {
    var timeout: Long = 30_000
    var followRedirects: Boolean = true
    val headers: MutableMap<String, String> = mutableMapOf()

    fun header(name: String, value: String) {
        headers[name] = value
    }
}

// Builder function
fun httpClient(configure: HttpClient.() -> Unit): HttpClient {
    return HttpClient().apply(configure)
}

// Usage
val client = httpClient {
    timeout = 60_000
    followRedirects = false
    header("User-Agent", "MyApp/1.0")
}

Nested DSL example:

@DslMarker
annotation class HtmlDsl

@HtmlDsl
abstract class Tag(val name: String) {
    private val children = mutableListOf<Tag>()

    protected fun <T : Tag> initTag(tag: T, init: T.() -> Unit): T {
        tag.init()
        children.add(tag)
        return tag
    }
}

class HTML : Tag("html") {
    fun head(init: Head.() -> Unit) = initTag(Head(), init)
    fun body(init: Body.() -> Unit) = initTag(Body(), init)
}

class Head : Tag("head") {
    fun title(init: Title.() -> Unit) = initTag(Title(), init)
}

class Title : Tag("title")
class Body : Tag("body")

// Usage
fun html(init: HTML.() -> Unit): HTML = HTML().apply(init)

val page = html {
    head {
        title { }
    }
    body { }
}

Sealed Classes for Type Safety

Use sealed classes for restricted hierarchies:

// Good: Sealed class for results
sealed interface Result<out T> {
    data class Success<T>(val value: T) : Result<T>
    data class Failure(val error: Throwable) : Result<Nothing>
}

// Exhaustive when expressions
fun <T> Result<T>.getOrThrow(): T = when (this) {
    is Result.Success -> value
    is Result.Failure -> throw error
}

// Good: Sealed class for events
sealed class NetworkEvent {
    data class Connected(val connectionId: String) : NetworkEvent()
    data class Disconnected(val reason: String) : NetworkEvent()
    data class MessageReceived(val message: String) : NetworkEvent()
    data object Reconnecting : NetworkEvent()
}

Inline Functions for Performance

Use inline for higher-order functions:

// Good: Inline function with reified type
inline fun <reified T> Any.asOrNull(): T? = this as? T

// Usage without explicit type
val string: String? = someObject.asOrNull()

// Good: Inline with lambda
inline fun <T> measureTime(block: () -> T): Pair<T, Long> {
    val start = System.currentTimeMillis()
    val result = block()
    val time = System.currentTimeMillis() - start
    return result to time
}

// Good: Inline value classes for type safety
@JvmInline
value class UserId(val value: String)

@JvmInline
value class Email(val value: String)

// No runtime overhead, but type-safe
fun sendEmail(userId: UserId, email: Email) { }

Operator Overloading

Use operator functions judiciously:

data class Vector(val x: Double, val y: Double) {
    operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)
    operator fun minus(other: Vector) = Vector(x - other.x, y - other.y)
    operator fun times(scalar: Double) = Vector(x * scalar, y * scalar)
    operator fun unaryMinus() = Vector(-x, -y)
}

// Usage
val v1 = Vector(1.0, 2.0)
val v2 = Vector(3.0, 4.0)
val sum = v1 + v2
val scaled = v1 * 2.0

Module Organization

Standard Library Structure

my-kotlin-library/
├── build.gradle.kts
├── gradle.properties
├── settings.gradle.kts
├── src/
│   ├── main/kotlin/
│   │   └── com/example/library/
│   │       ├── Library.kt           # Public API
│   │       ├── Models.kt            # Data classes
│   │       ├── Extensions.kt        # Extension functions
│   │       ├── Dsl.kt              # DSL builders
│   │       └── internal/           # Internal implementation
│   │           └── Utils.kt
│   └── test/kotlin/
│       └── com/example/library/
│           ├── LibraryTest.kt
│           └── DslTest.kt
├── docs/
│   └── index.md
└── README.md

Public API Organization

Main library file (Library.kt):

package com.example.library

// Public API exports
public fun createClient(): Client = ClientImpl()

public interface Client {
    fun connect(): Result<Unit>
    fun disconnect()
}

// Internal implementation
internal class ClientImpl : Client {
    override fun connect(): Result<Unit> = TODO()
    override fun disconnect() = TODO()
}

Visibility Modifiers

// Public - part of API (default, but explicit is clearer)
public fun publicFunction() {}

// Internal - visible within module only
internal fun internalFunction() {}

// Private - visible within file only
private fun privateFunction() {}

// Protected - visible in class and subclasses
protected open class Base {
    protected fun protectedFunction() {}
}

Testing with Kotest

Test Structure

import io.kotest.core.spec.style.FunSpec
import io.kotest.core.spec.style.StringSpec
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.string.shouldStartWith

// String spec (simple)
class SimpleTest : StringSpec({
    "string length should be 5" {
        "hello".length shouldBe 5
    }

    "string should start with h" {
        "hello" shouldStartWith "h"
    }
})

// Fun spec (descriptive)
class CalculatorTest : FunSpec({
    test("addition should work correctly") {
        val result = 2 + 2
        result shouldBe 4
    }

    context("division") {
        test("dividing by non-zero should work") {
            10 / 2 shouldBe 5
        }

        test("dividing by zero should throw") {
            shouldThrow<ArithmeticException> {
                10 / 0
            }
        }
    }
})

// Behavior spec (BDD)
class ClientTest : BehaviorSpec({
    given("a connected client") {
        val client = createClient()
        client.connect()

        `when`("sending a message") {
            val result = client.send("test")

            then("it should succeed") {
                result shouldBe Success
            }
        }

        `when`("disconnecting") {
            client.disconnect()

            then("it should be disconnected") {
                client.isConnected shouldBe false
            }
        }
    }
})

Property-Based Testing

import io.kotest.core.spec.style.FunSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.*
import io.kotest.property.checkAll

class PropertyTest : FunSpec({
    test("string reverse is involutive") {
        checkAll<String> { str ->
            str.reversed().reversed() shouldBe str
        }
    }

    test("addition is commutative") {
        checkAll(Arb.int(), Arb.int()) { a, b ->
            a + b shouldBe b + a
        }
    }

    test("custom generator") {
        val emailArb = arbitrary { rs ->
            val name = Arb.string(5..10).bind()
            val domain = Arb.string(5..10).bind()
            "$name@$domain.com"
        }

        checkAll(emailArb) { email ->
            email shouldContain "@"
        }
    }
})

JUnit Integration

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class JUnitTest {
    private lateinit var client: Client

    @BeforeEach
    fun setup() {
        client = createClient()
    }

    @Test
    @DisplayName("Client should connect successfully")
    fun `client should connect`() {
        val result = client.connect()
        assertTrue(result.isSuccess)
    }

    @ParameterizedTest
    @ValueSource(strings = ["hello", "world", "test"])
    fun `should handle various inputs`(input: String) {
        val result = process(input)
        assertTrue(result.isNotEmpty())
    }
}

KDoc Documentation

Documentation Style

/**
 * Creates an HTTP client with the specified configuration.
 *
 * This function uses a DSL to configure the client. All settings
 * are optional and have sensible defaults.
 *
 * @param configure A lambda with receiver for configuring the client
 * @return A configured HTTP client instance
 *
 * @sample com.example.samples.basicClientExample
 * @see HttpClient for available configuration options
 * @since 1.0.0
 *
 * Example:
 * ```kotlin
 * val client = httpClient {
 *     timeout = 60_000
 *     followRedirects = false
 *     header("User-Agent", "MyApp/1.0")
 * }
 * ```
 */
fun httpClient(configure: HttpClient.() -> Unit): HttpClient {
    return HttpClient().apply(configure)
}

/**
 * Represents the result of an operation.
 *
 * @param T The type of the successful result value
 * @property value The result value if successful
 * @property error The error if failed
 *
 * @constructor Creates a result instance
 */
sealed class Result<out T> {
    /**
     * Represents a successful result.
     *
     * @property value The successful value
     */
    data class Success<T>(val value: T) : Result<T>()

    /**
     * Represents a failed result.
     *
     * @property error The error that caused the failure
     */
    data class Failure(val error: Throwable) : Result<Nothing>()
}

Dokka Configuration

build.gradle.kts:

plugins {
    id("org.jetbrains.dokka") version "1.9.20"
}

tasks.dokkaHtml {
    outputDirectory.set(buildDir.resolve("dokka"))

    dokkaSourceSets {
        named("main") {
            moduleName.set("My Library")
            includes.from("docs/index.md")

            sourceLink {
                localDirectory.set(file("src/main/kotlin"))
                remoteUrl.set(
                    URL("https://github.com/user/repo/tree/main/src/main/kotlin")
                )
                remoteLineSuffix.set("#L")
            }

            externalDocumentationLink {
                url.set(URL("https://kotlinlang.org/api/latest/jvm/stdlib/"))
            }
        }
    }
}

Publishing to Maven Central

Setup Requirements

  1. Sonatype OSSRH Account: Sign up at https://issues.sonatype.org
  2. GPG Key: Generate and publish a GPG key
  3. Gradle Configuration: Configure publishing plugin

GPG Key Setup

# Generate key
gpg --gen-key

# List keys
gpg --list-keys

# Export public key
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID

# Export private key
gpg --export-secret-keys YOUR_KEY_ID > secring.gpg

Publishing Plugin Configuration

build.gradle.kts:

plugins {
    `maven-publish`
    signing
    id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
}

nexusPublishing {
    repositories {
        sonatype {
            nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
            snapshotRepositoryUrl.set(
                uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")
            )
            username.set(project.findProperty("sonatypeUsername") as String?)
            password.set(project.findProperty("sonatypePassword") as String?)
        }
    }
}

publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])

            artifact(tasks["kotlinSourcesJar"])
            artifact(tasks["dokkaJavadocJar"])

            pom {
                name.set(project.name)
                description.set("Library description")
                url.set("https://github.com/user/repo")

                licenses {
                    license {
                        name.set("The Apache License, Version 2.0")
                        url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
                    }
                }

                developers {
                    developer {
                        id.set("userid")
                        name.set("User Name")
                        email.set("user@example.com")
                    }
                }

                scm {
                    connection.set("scm:git:git://github.com/user/repo.git")
                    developerConnection.set("scm:git:ssh://github.com/user/repo.git")
                    url.set("https://github.com/user/repo")
                }
            }
        }
    }
}

signing {
    sign(publishing.publications["maven"])
}

val dokkaJavadocJar by tasks.registering(Jar::class) {
    dependsOn(tasks.dokkaJavadoc)
    from(tasks.dokkaJavadoc.flatMap { it.outputDirectory })
    archiveClassifier.set("javadoc")
}

val kotlinSourcesJar by tasks.registering(Jar::class) {
    from(sourceSets["main"].allSource)
    archiveClassifier.set("sources")
}

Publishing Workflow

# 1. Build the project
./gradlew build

# 2. Publish to local Maven for testing
./gradlew publishToMavenLocal

# 3. Publish to Sonatype staging
./gradlew publishToSonatype

# 4. Close and release staging repository
./gradlew closeAndReleaseSonatypeStagingRepository

# Or all in one
./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository

Binary Compatibility

API Validation Plugin

build.gradle.kts:

plugins {
    id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.14.0"
}

apiValidation {
    ignoredProjects.add("test-utils")

    nonPublicMarkers.add("com.example.InternalApi")

    validationDisabled = false
}

Usage

# Generate API dump
./gradlew apiDump

# Check API compatibility
./gradlew apiCheck

Commit the API dump:

my-library/
├── api/
│   └── my-library.api

Anti-Patterns

1. Overusing Extension Functions

// Bad: Extension on Any
fun Any.doSomething() { }

// Good: Specific type
fun String.doSomething() { }

2. Breaking API with Default Parameters

// v1.0.0
fun connect(host: String, port: Int = 8080)

// v1.1.0 - WRONG! This is breaking
fun connect(host: String, port: Int = 8080, timeout: Long)

// v1.1.0 - Correct: Add overload or new default at end
fun connect(host: String, port: Int = 8080, timeout: Long = 30000)

3. Not Using Visibility Modifiers

// Bad: Everything public by default
class InternalHelper {
    fun helpfulMethod() { }
}

// Good: Explicit visibility
internal class InternalHelper {
    internal fun helpfulMethod() { }
}

4. Mutable Public Collections

// Bad: Mutable public property
val items: MutableList<String> = mutableListOf()

// Good: Immutable public interface
private val _items: MutableList<String> = mutableListOf()
val items: List<String> get() = _items

// Or use read-only collection
val items: List<String> = listOf()

Common Dependencies

Coroutines

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
}

Serialization

plugins {
    kotlin("plugin.serialization") version "1.9.22"
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}

Testing

dependencies {
    testImplementation(kotlin("test"))
    testImplementation("io.kotest:kotest-runner-junit5:5.8.0")
    testImplementation("io.kotest:kotest-assertions-core:5.8.0")
    testImplementation("io.kotest:kotest-property:5.8.0")
    testImplementation("io.mockk:mockk:1.13.9")
}

References