This skill should be used when the user asks to "write Kotlin code", "create a Kotlin class", "set up a Kotlin project", "review Kotlin code", "refactor Kotlin", "use coroutines", "fix Kotlin style", "set up Detekt", "configure ktlint", "add static analysis", "set up code linting", or when generating any Kotlin source code. Provides modern Kotlin 2.1+ best practices covering null safety, coroutines, data modeling, error handling, idiomatic patterns, and static analysis (Detekt, ktlint). Does not cover any specific library or framework.
72
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Modern Kotlin best practices for writing concise, safe, and idiomatic code. Targets Kotlin 2.1+ on the JVM — language and stdlib only, no frameworks or libraries.
references/type-system.md — Generics, variance, smart casts, inline/value classes, Nothing, SAM conversionsreferences/patterns.md — Scope functions, sealed hierarchies, delegation, extensions, DSL builders, domain modeling, error handling guidereferences/coroutines.md — Flow, StateFlow/SharedFlow, Channels, exception handling, cancellation, testingreferences/project-structure.md — build.gradle.kts, multi-module, compiler options, testing setupreferences/static-analysis.md — Detekt (setup, detekt.yml, rule sets, custom rules, baseline, Compose rules), ktlint (setup, .editorconfig, standard rules, Spotless), Detekt vs ktlint comparison, multi-module convention plugin, pre-commit hooksjava.time.LocalDateTime) in the code body. Use import aliases for naming conflicts. No wildcard imports.| Element | Convention | Example |
|---|---|---|
| Package | lowercase | com.example.userdata |
| Class / Object | PascalCase | UserAccount, JsonParser |
| Function / Property | camelCase | getUserById(), isActive |
Constant (const val) | UPPER_SNAKE | MAX_RETRIES |
| Type parameter | Single uppercase | T, K, V |
| Enum entry | UPPER_SNAKE | Status.ACTIVE |
| Backing property | _prefixed | private val _items |
| File name | PascalCase.kt | UserService.kt |
| Boolean | Prefix is, has, can, should | isValid, hasPermission |
val name: String? = findUser()?.name // safe call
val length = name?.length ?: 0 // elvis — default for nullNEVER use !! to silence the compiler — it crashes at runtime. Acceptable only with a provable invariant and a comment explaining why.
user?.let { sendEmail(it) } // nullable transform
requireNotNull(id) { "id must not be null" } // precondition + smart cast
val names: List<String> = users.mapNotNull { it.name } // filter nullsKotlin infers types aggressively. Annotate explicitly for public API, when the inferred type is too broad, or when readability benefits.
// Inferred — fine for locals
val count = items.size
// Explicit — public API
fun findUser(id: String): User? { ... }For generics (in/out variance, star projection, reified types), type aliases, smart casts, and value classes, see references/type-system.md.
data class — value containersdata class User(val id: String, val name: String, val email: String)MUST use val properties. ALWAYS prefer data classes over Map<String, Any> — maps lose type safety, autocompletion, and refactoring support.
sealed class / sealed interface — restricted hierarchiessealed interface Result<out T> {
data class Success<T>(val value: T) : Result<T>
data class Failure(val error: Throwable) : Result<Nothing>
}Enables exhaustive when. Prefer sealed interface when subtypes don't share state.
value class — zero-cost wrappers@JvmInline
value class UserId(val value: String)
@JvmInline
value class Email(val value: String) {
init { require(value.contains("@")) { "Invalid email: $value" } }
}Use to prevent primitive obsession — the compiler catches swapped parameters.
Never use Double/Float for monetary values. Use BigDecimal (construct from String, never Double) with explicit MathContext, or Long (cents) with a value class wrapper. See references/patterns.md for full patterns.
// Custom hierarchy
open class AppException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
class ValidationException(message: String) : AppException(message)
class NotFoundException(message: String) : AppException(message)
// runCatching for functional error handling
val name = runCatching { fetchUser(id) }.map { it.name }.getOrDefault("Unknown")
// Sealed result for expected business outcomes
sealed interface FetchResult {
data class Found(val user: User) : FetchResult
data object NotFound : FetchResult
data class Error(val reason: String) : FetchResult
}Rules:
require() for argument validation, check() for state validation.Throwable unless re-throwing.references/patterns.md for the full error handling decision guide.suspend fun fetchUser(id: String): User = httpClient.get("users/$id")
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { fetchUser(userId) }
val orders = async { fetchOrders(userId) }
Dashboard(user.await(), orders.await())
}Rules:
GlobalScope.coroutineScope when all must succeed; supervisorScope when partial failure is OK.launch for fire-and-forget; async when you need the result.CancellationException — ALWAYS rethrow it.withContext(Dispatchers.IO) for blocking I/O; Dispatchers.Default for CPU work.For Flow, Channels, exception handling, cancellation patterns, timeouts, and testing, see references/coroutines.md.
project-name/
├── build.gradle.kts
├── settings.gradle.kts
├── gradle/libs.versions.toml
├── src/
│ ├── main/kotlin/com/example/project/
│ └── test/kotlin/com/example/project/
└── gradlewlibs.versions.toml).For build.gradle.kts config, multi-module setup, compiler options, and testing, see references/project-structure.md.
buildList / buildMap / buildString for constructing collections and strings.groupBy, associateBy, partition, flatMap for collection transformations.Sequence for lazy evaluation of large collections.use for auto-closeable resource management.users.filter { it.age >= 18 }.map { it.name }| Function | Receiver | Returns | Use for |
|---|---|---|---|
apply | this | receiver | Object configuration |
let | it | lambda result | Nullable transforms, scoping |
run | this | lambda result | Object computation |
also | it | receiver | Side effects |
with | this | lambda result | Grouping calls |
For the complete decision guide, see references/patterns.md.
For full patterns, see references/patterns.md.
| Mistake | Fix |
|---|---|
Using !! to silence nullability | Use ?., ?:, let, or redesign to be non-null |
| Platform types without annotation | Add explicit nullability at Java boundaries |
var in data classes | Use val — copy with copy() |
Catching Throwable | Catch specific exceptions; rethrow CancellationException |
GlobalScope.launch | Use structured concurrency |
| Mutable collections in public API | Expose List, not MutableList; backing property pattern |
| Inline fully-qualified types | Import at top; use aliases for conflicts |
Map<String, Any> as data holder | Define a data class |
Double for money | BigDecimal with MathContext, or Long (cents) |
| Long functions (>15 lines) | Extract named private functions |
| Hard-coded dependencies | Constructor injection; depend on interfaces |
when without exhaustive check | Use sealed types or add else branch |
298fce7
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.