# Standards Kotlin

> Kotlin coding standards for modern applications. Includes naming conventions, coroutines, flows, modern Kotlin 2.3.0 features, and recommended tooling.

- **Type:** Skill
- **Install:** `agentstack add skill-b33eep-claude-code-setup-standards-kotlin`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [b33eep](https://agentstack.voostack.com/s/b33eep)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [b33eep](https://github.com/b33eep)
- **Source:** https://github.com/b33eep/claude-code-setup/tree/main/skills/standards-kotlin
- **Website:** https://b33eep.github.io/claude-code-setup/

## Install

```sh
agentstack add skill-b33eep-claude-code-setup-standards-kotlin
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Kotlin Coding Standards

## Core Principles

1. **Explicitness**: Explicit code over implicit magic
2. **Readability**: Readable code over clever tricks
3. **Null Safety**: Embrace Kotlin's null safety system
4. **Immutability**: Prefer `val` over `var`, immutable collections
5. **Expressiveness**: Use Kotlin's expressive features (data classes, sealed classes)
6. **DRY**: Don't Repeat Yourself - but keep it simple

## General Rules

- **Prefer `val` over `var`**: Immutability by default
- **Use data classes**: For simple data holders
- **Sealed classes/interfaces**: For type-safe state modeling
- **Early returns**: Avoid deep nesting
- **Descriptive names**: Clear, meaningful names
- **Minimal changes**: Only change relevant code
- **No over-engineering**: Keep it simple
- **Minimal comments**: Self-explanatory code. Comments for "why", not "what"

## Naming Conventions

| Element | Convention | Example |
|---------|------------|---------|
| Classes | PascalCase | `UserService`, `OrderRepository` |
| Interfaces | PascalCase | `UserRepository`, `PaymentProcessor` |
| Functions | camelCase | `getUserById`, `calculateTotal` |
| Properties | camelCase | `firstName`, `totalAmount` |
| Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT`, `DEFAULT_TIMEOUT` |
| Packages | lowercase.dot.separated | `com.example.service`, `com.example.repository` |
| Files | PascalCase.kt | `UserService.kt`, `OrderRepository.kt` |
| Test Classes | ClassNameTest | `UserServiceTest`, `OrderRepositoryTest` |
| Test Functions | backtick names | `` `should return user when id exists` `` |

## Project Structure

### Gradle Kotlin DSL Project (Recommended)
```
myproject/
├── build.gradle.kts
├── settings.gradle.kts
├── gradle.properties
├── src/
│   ├── main/
│   │   ├── kotlin/
│   │   │   └── com/example/myapp/
│   │   │       ├── Application.kt          # Main entry point
│   │   │       ├── config/
│   │   │       │   └── AppConfig.kt        # Configuration
│   │   │       ├── domain/
│   │   │       │   └── User.kt             # Domain models
│   │   │       ├── repository/
│   │   │       │   └── UserRepository.kt   # Data access
│   │   │       ├── service/
│   │   │       │   └── UserService.kt      # Business logic
│   │   │       └── api/
│   │   │           └── UserController.kt   # REST endpoints
│   │   └── resources/
│   │       ├── application.conf
│   │       └── logback.xml
│   └── test/
│       ├── kotlin/
│       │   └── com/example/myapp/
│       │       ├── service/
│       │       │   └── UserServiceTest.kt
│       │       └── repository/
│       │           └── UserRepositoryTest.kt
│       └── resources/
│           └── application-test.conf
└── README.md
```

### Maven Project (Alternative)
```
myproject/
├── pom.xml
├── src/
│   ├── main/
│   │   └── kotlin/...      # Same structure as Gradle
│   └── test/
│       └── kotlin/...      # Same structure as Gradle
└── README.md
```

## Modern Kotlin Features

> **Recommended:** Use Kotlin 2.3.0 (latest LTS) for new projects with K2 compiler enabled by default.

### K2 Compiler (Stable since 2.0)

The K2 compiler brings significant performance improvements and faster compilation times.

**Features:**
- Faster compilation (up to 2x)
- Better smart casts
- Improved type inference
- Unified architecture for all platforms

**Enabled by default in Kotlin 2.3.0** - no configuration needed.

### Data Classes

Use data classes for immutable data holders.

```kotlin
// Data class - automatic equals, hashCode, toString, copy, componentN
data class User(
    val id: String,
    val name: String,
    val email: String,
    val age: Int
)

// Usage
val user = User("1", "John Doe", "john@example.com", 30)

// Copy with changes
val updatedUser = user.copy(age = 31)

// Destructuring
val (id, name, email, age) = user
println("User: $name ($email)")
```

### Sealed Classes/Interfaces (Exhaustive When)

Use sealed classes for type-safe state modeling with exhaustive `when` expressions.

```kotlin
// Sealed interface for result types
sealed interface Result {
    data class Success(val data: T) : Result
    data class Error(val message: String, val cause: Throwable? = null) : Result
    data object Loading : Result
}

// Exhaustive when - compiler ensures all cases are handled
fun  handleResult(result: Result) {
    when (result) {
        is Result.Success -> println("Success: ${result.data}")
        is Result.Error -> println("Error: ${result.message}")
        Result.Loading -> println("Loading...")
        // No else needed - compiler knows all cases
    }
}

// Usage
val result: Result = Result.Success(user)
handleResult(result)
```

### Inline Value Classes (Zero-Cost Wrappers)

Use inline value classes for type-safe wrappers without runtime overhead.

```kotlin
// Inline value class - no boxing overhead
@JvmInline
value class UserId(val value: String)

@JvmInline
value class Email(val value: String) {
    init {
        require(value.contains("@")) { "Invalid email" }
    }
}

// Usage - type-safe, no runtime cost
fun getUserById(id: UserId): User = TODO()
fun sendEmail(email: Email): Unit = TODO()

val userId = UserId("123")
val email = Email("user@example.com")
```

### Context Receivers (Experimental, 2.2+)

Context receivers allow implicit parameters for cleaner DSLs.

**Enable with:**
```kotlin
// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xcontext-receivers")
    }
}
```

**Usage:**
```kotlin
interface Logger {
    fun log(message: String)
}

// Function with context receiver
context(Logger)
fun processUser(user: User) {
    log("Processing user: ${user.name}")
    // ...
}

// Call with context
val logger = object : Logger {
    override fun log(message: String) = println(message)
}

with(logger) {
    processUser(user)
}
```

### Explicit Backing Fields (Experimental, 2.3)

Simplifies backing property pattern - define implementation type within property scope.

**Enable with:**
```kotlin
// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xexplicit-backing-fields")
    }
}
```

**Before:**
```kotlin
private val _users = MutableStateFlow>(emptyList())
val users: StateFlow> = _users
```

**After:**
```kotlin
val users: StateFlow> field = MutableStateFlow(emptyList())
```

### UUID API (Experimental, 2.3)

Built-in UUID support without external dependencies.

**Enable with:**
```kotlin
@OptIn(ExperimentalUuidApi::class)
```

**Usage:**
```kotlin
import kotlin.uuid.Uuid
import kotlin.uuid.ExperimentalUuidApi

@OptIn(ExperimentalUuidApi::class)
fun generateUserId(): Uuid {
    return Uuid.generateV4()
}

@OptIn(ExperimentalUuidApi::class)
fun parseUserId(id: String): Uuid? {
    return Uuid.parseOrNull(id)
}

// V7 UUIDs (time-based, sortable)
@OptIn(ExperimentalUuidApi::class)
fun generateTimeBasedId(): Uuid {
    return Uuid.generateV7()
}
```

## Coroutines & Concurrency

### Structured Concurrency

Always use structured concurrency - never use `GlobalScope`.

```kotlin
import kotlinx.coroutines.*

// GOOD - Structured concurrency
suspend fun fetchUserData(userId: String): UserData = coroutineScope {
    val userDeferred = async { fetchUser(userId) }
    val ordersDeferred = async { fetchOrders(userId) }

    UserData(
        user = userDeferred.await(),
        orders = ordersDeferred.await()
    )
}

// BAD - GlobalScope leaks
fun fetchUserDataBad(userId: String) {
    GlobalScope.launch {  // Don't use GlobalScope!
        // ...
    }
}
```

### Dispatchers

Use appropriate dispatchers for different workloads.

```kotlin
// Dispatchers.Default - CPU-intensive work
withContext(Dispatchers.Default) {
    // Heavy computation
    processLargeDataset(data)
}

// Dispatchers.IO - I/O operations (network, disk)
withContext(Dispatchers.IO) {
    // Network call
    apiClient.fetchData()
}

// Dispatchers.Main - UI updates (Android/Desktop)
withContext(Dispatchers.Main) {
    updateUI(data)
}

// Dispatchers.Unconfined - Advanced use cases only
```

### launch vs async

```kotlin
// launch - fire and forget, returns Job
fun processInBackground() = coroutineScope {
    launch {
        // No result needed
        sendNotification()
    }
}

// async - returns Deferred, await for result
suspend fun fetchMultipleResources() = coroutineScope {
    val users = async { fetchUsers() }
    val orders = async { fetchOrders() }

    CombinedData(
        users = users.await(),
        orders = orders.await()
    )
}
```

### Cancellation & Exception Handling

```kotlin
// Cancellation-aware code
suspend fun longRunningTask() = coroutineScope {
    repeat(100) { i ->
        ensureActive()  // Check for cancellation
        delay(100)
        println("Step $i")
    }
}

// Exception handling with supervisorScope
suspend fun fetchDataSafely(): Result = try {
    supervisorScope {
        val data = async { fetchData() }
        Result.Success(data.await())
    }
} catch (e: Exception) {
    Result.Error("Failed to fetch data", e)
}

// CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught exception: $exception")
}

val scope = CoroutineScope(SupervisorJob() + handler)
```

## Flow API

### StateFlow vs SharedFlow vs MutableStateFlow

```kotlin
// StateFlow - single value, always has current value, conflates updates
class UserViewModel {
    private val _userName = MutableStateFlow("")
    val userName: StateFlow = _userName.asStateFlow()

    fun updateUserName(name: String) {
        _userName.value = name
    }
}

// SharedFlow - event stream, can replay, doesn't conflate
class EventBus {
    private val _events = MutableSharedFlow(
        replay = 0,      // Don't replay events
        extraBufferCapacity = 64
    )
    val events: SharedFlow = _events.asSharedFlow()

    suspend fun emit(event: Event) {
        _events.emit(event)
    }
}

// Hot vs Cold flows
// StateFlow/SharedFlow = Hot (emit regardless of collectors)
// flow { } = Cold (only emit when collected)
```

### Flow Operators

```kotlin
// Transform flows
val userNames: Flow = users
    .map { it.name }
    .filter { it.isNotEmpty() }
    .distinctUntilChanged()

// Combine flows
val combinedData = combine(users, orders) { users, orders ->
    CombinedData(users, orders)
}

// FlatMap variants
val allOrders: Flow = users
    .flatMapConcat { user -> fetchOrders(user.id) }  // Sequential
    // .flatMapMerge { user -> fetchOrders(user.id) }  // Concurrent
    // .flatMapLatest { user -> fetchOrders(user.id) }  // Cancel previous

// Error handling
val safeData: Flow = dataFlow
    .catch { e -> emit(Data.Empty) }
    .retry(3)
```

### Flow Collection

```kotlin
// Collect in coroutine
lifecycleScope.launch {
    userViewModel.userName.collect { name ->
        updateUI(name)
    }
}

// collectLatest - cancel previous collection on new emission
lifecycleScope.launch {
    searchQuery.collectLatest { query ->
        // Cancelled if new query arrives
        val results = searchRepository.search(query)
        updateResults(results)
    }
}

// Single value
val user = userFlow.first()  // First value
val user = userFlow.firstOrNull()  // Or null if empty
```

### Flow Best Practices

1. **Single source of truth**: Expose `StateFlow`/`SharedFlow`, keep `MutableStateFlow`/`MutableSharedFlow` private
2. **Use `collectLatest` for UI**: Cancel previous work on new emissions
3. **Use `stateIn` for cold-to-hot conversion**:
```kotlin
val data: StateFlow = dataRepository.getData()
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = Data.Empty
    )
```

## Null Safety

### Safe Calls, Elvis, and !! Operator

```kotlin
// Safe call (?.) - returns null if receiver is null
val length: Int? = name?.length

// Elvis operator (?:) - default value
val length: Int = name?.length ?: 0

// !! operator - throws NPE if null (use sparingly!)
val length: Int = name!!.length  // Only if you're 100% sure it's not null

// Safe cast (as?)
val user: User? = obj as? User

// let for null checks
name?.let { n ->
    println("Name: $n")
}
```

### lateinit vs lazy

```kotlin
// lateinit - non-null var, initialized later (must be var)
class MyClass {
    lateinit var repository: Repository

    fun init(repo: Repository) {
        repository = repo
    }

    // Check if initialized
    fun isInitialized() = ::repository.isInitialized
}

// lazy - initialized on first access (must be val)
class MyClass {
    val expensiveValue: String by lazy {
        // Computed only once, on first access
        computeExpensiveValue()
    }
}
```

### Nullable Types vs Optional

```kotlin
// GOOD - Use nullable types (Kotlin-native)
fun findUser(id: String): User? {
    return repository.findById(id)
}

// BAD - Don't use Java's Optional in Kotlin
fun findUserBad(id: String): Optional {  // Avoid
    return Optional.ofNullable(repository.findById(id))
}

// When interoping with Java, convert at boundary
fun getUserFromJava(id: String): User? {
    return javaService.getUser(id).orElse(null)
}
```

### Backing Properties (Standard Pattern)

Use underscore prefix for private mutable backing properties.

```kotlin
// Standard pattern for exposing read-only property
class UserRepository {
    private val _users = mutableListOf()
    val users: List get() = _users

    fun addUser(user: User) {
        _users.add(user)
    }
}

// StateFlow pattern
class UserViewModel {
    private val _state = MutableStateFlow(UiState.Loading)
    val state: StateFlow = _state.asStateFlow()

    fun updateState(newState: UiState) {
        _state.value = newState
    }
}

// Alternative: explicit backing fields (experimental, Kotlin 2.3+)
// Requires: -Xexplicit-backing-fields compiler flag
val users: StateFlow> field = MutableStateFlow(emptyList())
```

## Scope Functions

Kotlin provides five scope functions: `let`, `run`, `with`, `apply`, `also`. Choose based on context object reference and return value.

| Function | Object Reference | Return Value | Use Case |
|----------|-----------------|--------------|----------|
| `let` | `it` | Lambda result | Null checks, transformations |
| `run` | `this` | Lambda result | Object configuration + computation |
| `with` | `this` | Lambda result | Group function calls on object |
| `apply` | `this` | Context object | Object initialization |
| `also` | `it` | Context object | Side effects |

### let - Null Checks & Transformations

```kotlin
// Null check with let
val length: Int? = name?.let { it.length }

// Chain with let
val result = value?.let { v ->
    processValue(v)
}?.let { processed ->
    saveResult(processed)
}

// Execute block only if non-null
user?.let { u ->
    println("User: ${u.name}")
    sendWelcomeEmail(u)
}

// Transform value
val uppercaseName = name?.let { it.uppercase() }
```

### apply - Object Initialization

```kotlin
// Object initialization (returns `this`)
val user = User().apply {
    name = "John Doe"
    email = "john@example.com"
    age = 30
}

// Configure and return
val intent = Intent(context, DetailActivity::class.java).apply {
    putExtra("id", userId)
    flags = Intent.FLAG_ACTIVITY_NEW_TASK
}

// Builder pattern style
val dialog = AlertDialog.Builder(context).apply {
    setTitle("Confirm")
    setMessage("Are you sure?")
    setPositiveButton("Yes") { _, _ -> confirm() }
}.create()
```

### also - Side Effects

```kotlin
// Side effects (returns `this`)
val numbers = mutableListOf(1, 2, 3).also {
    println("List created with ${it.size} elements")
}

// Debug chain
val result = processData(input)
    .also { println("Intermediate result: $it") }
    .transformData()
    .also { println("Final result: $it") }

// Multiple operations
val user = createUser().also {
    logUserCreation(it)
    sendWelcomeEmail(it)
}
```

### run - Computations

```kotlin
// Compute value (returns lambda result)
val result = run {
    val x = computeX()
    val y = computeY()
    x + y
}

// Null check with run
val

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [b33eep](https://github.com/b33eep)
- **Source:** [b33eep/claude-code-setup](https://github.com/b33eep/claude-code-setup)
- **License:** MIT
- **Homepage:** https://b33eep.github.io/claude-code-setup/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-b33eep-claude-code-setup-standards-kotlin
- Seller: https://agentstack.voostack.com/s/b33eep
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
