kotlin-guide

Applies to: Kotlin 1.9+, JVM 17+, Coroutines, Android, Server-side

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "kotlin-guide" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-kotlin-guide

Kotlin Guide

Applies to: Kotlin 1.9+, JVM 17+, Coroutines, Android, Server-side

Core Principles

  • Null Safety: Leverage the type system to eliminate null pointer exceptions at compile time

  • Conciseness: Use data classes, scope functions, and destructuring to reduce boilerplate

  • Coroutines for Concurrency: Structured concurrency with coroutines, not raw threads

  • Immutability by Default: Prefer val over var , immutable collections over mutable

  • Interop Awareness: Write Kotlin-idiomatic code while maintaining clean Java interop boundaries

Guardrails

Version & Dependencies

  • Use Kotlin 1.9+ with Gradle Kotlin DSL (build.gradle.kts )

  • Target JVM 17+ for server-side, match Android minSdk for mobile

  • Pin Kotlin and kotlinx library versions together (BOM alignment)

  • Run ./gradlew dependencies to audit transitive dependencies

Code Style

  • Run ktlint before every commit (format + check)

  • Run detekt for static analysis (complexity, code smells)

  • Packages: lowercase, no underscores (com.example.userservice )

  • Classes: PascalCase | Functions/properties: camelCase | Constants: SCREAMING_SNAKE_CASE

  • Follow Kotlin Coding Conventions

Null Safety

  • Never use !! (not-null assertion) in production code

  • Use safe calls (?. ), elvis (?: ), and smart casts instead

  • Nullable types only at API boundaries (deserialization, Java interop)

  • Prefer requireNotNull() or checkNotNull() with meaningful messages over !!

  • Use ?.let { } for nullable transformations, not nested if checks

// BAD: crashes at runtime val length = name!!.length

// GOOD: safe call with fallback val length = name?.length ?: 0

// GOOD: explicit contract with clear error val validName = requireNotNull(name) { "User name must not be null for ID=$id" }

Coroutines

  • Always use structured concurrency (coroutineScope , supervisorScope )

  • Never use GlobalScope in production code

  • Use withContext(Dispatchers.IO) for blocking I/O operations

  • Always handle CancellationException correctly (rethrow, never swallow)

  • Set timeouts with withTimeout or withTimeoutOrNull for external calls

// BAD: unstructured, leaks coroutines GlobalScope.launch { fetchData() }

// GOOD: structured concurrency, respects parent lifecycle coroutineScope { val user = async { userService.getUser(id) } val orders = async { orderService.getOrders(id) } UserWithOrders(user.await(), orders.await()) }

Extension Functions

  • Use extension functions to add behavior, not to bypass access control

  • Keep extensions in a dedicated file (StringExtensions.kt , DateExtensions.kt )

  • Do not add extensions to Any or overly generic types

  • Prefer member functions for core behavior, extensions for utility/convenience

  • Document extensions with @receiver KDoc tag when purpose is not obvious

Project Structure

myproject/ ├── app/ # Application module or main entry │ └── src/main/kotlin/ ├── domain/ # Business logic, entities, use cases │ └── src/main/kotlin/ │ └── com/example/domain/ │ ├── model/ # Data classes, sealed classes │ ├── repository/ # Repository interfaces │ └── usecase/ # Business operations ├── data/ # Data layer implementations │ └── src/main/kotlin/ │ └── com/example/data/ │ ├── repository/ # Repository implementations │ ├── remote/ # API clients, DTOs │ └── local/ # Database, DAOs ├── presentation/ # UI or API controllers ├── build.gradle.kts ├── settings.gradle.kts └── gradle.properties

  • domain/ has zero framework dependencies (pure Kotlin)

  • data/ depends on domain/ , implements repository interfaces

  • presentation/ depends on domain/ , never imports from data/ directly

  • No circular module dependencies

Key Patterns

Data Classes & Value Classes

data class User( val id: UserId, val email: Email, val name: String, val role: Role = Role.VIEWER, ) { init { require(name.isNotBlank()) { "User name must not be blank" } } }

// Value classes for type-safe IDs (zero runtime overhead) @JvmInline value class UserId(val value: String) { init { require(value.isNotBlank()) { "UserId must not be blank" } } }

Sealed Classes & Interfaces

sealed interface Result<out T> { data class Success<T>(val data: T) : Result<T> data class Failure(val error: AppError) : Result<Nothing> }

sealed class AppError(val message: String) { data class NotFound(val resource: String, val id: String) : AppError("$resource with ID $id not found") data class Validation(val field: String, val reason: String) : AppError("Validation failed for $field: $reason") data class Unauthorized(val detail: String = "Authentication required") : AppError(detail) }

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

Scope Functions Quick Reference

Function Context Returns Use for

let

it

Lambda result Nullable transforms, scoped vars

run

this

Lambda result Compute value using object context

with

this

Lambda result Operate on non-null object

apply

this

Object itself Configure/build an object

also

it

Object itself Side effects (logging, events)

// apply: configure and return the object val request = HttpRequest.Builder().apply { url(endpoint) header("Authorization", "Bearer $token") timeout(Duration.ofSeconds(30)) }.build()

// also: side effects without modifying the chain val savedUser = userRepository.save(newUser).also { user -> logger.info("Created user: ${user.id}") }

Testing

Standards

  • Test files: *Test.kt in src/test/kotlin/ (mirror source package)

  • Use JUnit 5 with @Test , @Nested , @DisplayName

  • Use MockK for mocking (idiomatic Kotlin, supports coroutines)

  • Table-driven style with @ParameterizedTest and @MethodSource

  • Coverage target: >80% for business logic, >60% overall

  • Use runTest from kotlinx-coroutines-test for coroutine tests

Unit Test Pattern

class UserServiceTest { private val userRepository = mockk<UserRepository>() private val eventBus = mockk<EventBus>(relaxed = true) private val service = UserService(userRepository, eventBus)

@Nested
@DisplayName("createUser")
inner class CreateUser {
    @Test
    fun `creates user with valid input`() = runTest {
        coEvery { userRepository.save(any()) } returns mockUser
        val result = service.createUser(validInput)

        assertThat(result).isInstanceOf(Result.Success::class.java)
        coVerify { userRepository.save(any()) }
        coVerify { eventBus.publish(any&#x3C;UserCreatedEvent>()) }
    }

    @Test
    fun `fails with blank name`() = runTest {
        val result = service.createUser(blankNameInput)

        assertThat(result).isInstanceOf(Result.Failure::class.java)
        coVerify(exactly = 0) { userRepository.save(any()) }
    }
}

}

Parameterized Tests

companion object { @JvmStatic fun emailCases() = listOf( Arguments.of("user@example.com", true), Arguments.of("invalid-email", false), Arguments.of("", false), ) }

@ParameterizedTest(name = "email "{0}" valid={1}") @MethodSource("emailCases") fun validates email format(email: String, expected: Boolean) { val result = runCatching { Email(email) } assertThat(result.isSuccess).isEqualTo(expected) }

Tooling

Essential Commands

./gradlew build # Compile + test + check ./gradlew test # Run all tests ./gradlew test --tests "*.UserServiceTest" # Specific test class ./gradlew koverReport # Coverage report ./gradlew ktlintCheck # Check formatting ./gradlew ktlintFormat # Auto-fix formatting ./gradlew detekt # Static analysis ./gradlew dependencies # Dependency tree

Gradle Kotlin DSL Configuration

// build.gradle.kts plugins { kotlin("jvm") version "1.9.22" id("org.jlleitschuh.gradle.ktlint") version "12.1.0" id("io.gitlab.arturbosch.detekt") version "1.23.4" id("org.jetbrains.kotlinx.kover") version "0.7.5" }

kotlin { jvmToolchain(17) }

dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") testImplementation(kotlin("test")) testImplementation("io.mockk:mockk:1.13.9") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") testImplementation("org.assertj:assertj-core:3.25.1") }

detekt { config.setFrom("detekt.yml") buildUponDefaultConfig = true }

kover { verify { rule { minBound(80) } } }

Detekt Configuration

detekt.yml

complexity: LongMethod: threshold: 50 CyclomaticComplexMethod: threshold: 10 LargeClass: threshold: 300 LongParameterList: functionThreshold: 5 constructorThreshold: 8 style: ForbiddenComment: values: - "TODO(?!\(#\d+\))" # TODOs require issue reference MagicNumber: ignoreNumbers: ["-1", "0", "1", "2"] MaxLineLength: maxLineLength: 120 potential-bugs: UnsafeCast: active: true

References

For detailed patterns and examples, see:

  • references/patterns.md -- Coroutine patterns, sealed class hierarchies, DSL builders, Flow patterns

External References

  • Kotlin Coding Conventions

  • Kotlin Coroutines Guide

  • MockK Documentation

  • ktlint

  • detekt

  • Kover Coverage

  • Arrow (Functional Kotlin)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

blazor

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi

No summary provided by upstream source.

Repository SourceNeeds Review