Back to skills
SKILL.md
Lang Kotlin Library Dev
ASecurityKotlin-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.
- 8 stars
- 0 votes
- 0 copies
- 1 view
- Added February 8, 2026
Works with
Security analysis
100/100npx -y skills add aRustyDev/ai --skill lang-kotlin-library-dev --agent claude-codeAre you the author of Lang Kotlin Library Dev?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/arustydev-lang-kotlin-library-dev)---
name: lang-kotlin-library-dev
description: 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.
---
# 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
| Task | Command/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)
```kotlin
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
```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)
```kotlin
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:**
```kotlin
// 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:**
```kotlin
// 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:**
```kotlin
@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:**
```kotlin
// 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:**
```kotlin
// 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:**
```kotlin
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):**
```kotlin
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
```kotlin
// 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
```kotlin
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
```kotlin
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
```kotlin
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
```kotlin
/**
* 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:**
```kotlin
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
```bash
# 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:**
```kotlin
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
```bash
# 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:**
```kotlin
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
```bash
# 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
```kotlin
// Bad: Extension on Any
fun Any.doSomething() { }
// Good: Specific type
fun String.doSomething() { }
```
### 2. Breaking API with Default Parameters
```kotlin
// 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
```kotlin
// Bad: Everything public by default
class InternalHelper {
fun helpfulMethod() { }
}
// Good: Explicit visibility
internal class InternalHelper {
internal fun helpfulMethod() { }
}
```
### 4. Mutable Public Collections
```kotlin
// 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
```kotlin
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
}
```
### Serialization
```kotlin
plugins {
kotlin("plugin.serialization") version "1.9.22"
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
```
### Testing
```kotlin
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
- `meta-library-dev` - Foundational library patterns
- [Kotlin Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html)
- [Kotlin API Guidelines](https://kotlinlang.org/docs/api-guidelines-introduction.html)
- [Maven Central Publishing Guide](https://central.sonatype.org/publish/publish-guide/)
- [Dokka Documentation](https://kotlinlang.org/docs/dokka-introduction.html)
- [Kotest Documentation](https://kotest.io/)
Attribution
Comments
Loading comments…