
Description
Kotlin 2.x policy and pitfalls. Use when writing, reviewing, or refactoring Kotlin code — enforces coroutine-safety, Flow correctness, null-safety, and API-design rules that LLMs frequently get wrong.
SKILL.md
Kotlin — policy & pitfalls
Baseline Kotlin knowledge (data/sealed/value classes, scope functions, null-safety operators, extension functions, suspend, Flow, when exhaustiveness) is assumed. This skill does not teach the language — it encodes the project policy and the traps that keep appearing in code review.
Setup Check (run first)
Before writing non-trivial code:
- Kotlin version — target 2.x when possible. Check
build.gradle(.kts)(kotlin("jvm") version "2.x") orlibs.versions.toml. - JDK target —
kotlin { jvmToolchain(21) }orcompileOptions { targetCompatibility = JavaVersion.VERSION_21 }. Matters for virtual threads (21+) and records interop (17+). - Compiler plugins —
kotlin("plugin.spring"),kotlin("plugin.jpa"),kotlinx-serialization,kotlin("kapt")vscom.google.devtools.ksp. Missingplugin.spring→ final Spring classes can't be proxied. Missingplugin.jpa→InstantiationException: No default constructor. - Lint —
detekt/ktlintconfigured? Follow the existing rules; don't introduce new violations. - Build wrapper — use
./gradlew
MUST DO
- Null-safety via
?,?.,?:,let,requireNotNull. Use!!only when null is a true contract violation — document why on the same line. - Sealed hierarchies for closed result / state types (
sealed interface Result { data class Success(...); data class Failure(...) }) + exhaustivewhenwithoutelse. - Value classes (
@JvmInline value class) for domain identifiers (UserId,Email) — zero-overhead type-safety. data classonly for pure value types. Not for entities, services, or anything with behavior / lifecycle.- Structured concurrency — inject
CoroutineScope, usecoroutineScope { }/ framework scopes (viewModelScope). NeverGlobalScope.launch. - Always rethrow
CancellationExceptionin genericcatch (e: Exception)blocks — swallowing it disables cancellation. - Expose read-only Flow types —
val state: StateFlow<X> = _state.asStateFlow(). Never leakMutableStateFlow/MutableSharedFlowfrom an API. withContext(Dispatchers.IO)/Defaultfor blocking / CPU work inside suspend. Encapsulate dispatcher choice in the repository / data-source layer — not at call sites.- Immutability by default —
valovervar,ListoverMutableListin public API,copy()on data classes instead of mutation. - Named arguments for 3+ parameters — prevents silent argument swaps at call sites.
MUST NOT DO
- No
!!without a commented reason. Refactor to?.let { }/requireNotNull(x) { "why" }. - No
runBlockingin production — only inmainand tests. Inside a suspend function it's always a bug. - No
GlobalScope.launch/GlobalScope.async— leaks, no structured cancellation. - No swallowing
CancellationException.try/catch(Exception)without a cancellation rethrow silently disables cancellation. - No
.first()/.single()on a hotFlowwithout a timeout — a source that never emits hangs the coroutine forever. - No
async { }.await()sequentially when you want parallelism — it's the same as callingsuspenddirectly. UsecoroutineScope { val a = async { .. }; val b = async { .. }; a.await() + b.await() }. - No
Dispatchers.Main/Dispatchers.IOreferences from common / multiplatform code unless the module is JVM-only. - No platform-type leaks (
String!) in public API — annotate Java interop returns with@NotNull/@Nullableon the Java side, or cast explicitly. - No catching
Throwable— you'll catchOutOfMemoryError,StackOverflowError, and cancellation. UseExceptionand rethrow cancellation. - No
lateinit varon primitives or nullable types — compile error. UseDelegates.notNull()for primitives.
Reference Guide
| Load when | File |
|---|---|
| Async / reactive code — coroutines, Flow, StateFlow/SharedFlow, cancellation, testing | references/coroutines.md |
API design — scope functions, value/data/sealed classes, extension functions, inline/reified, delegates, Result<T> | references/idioms.md |
| Gradle / tooling — Kotlin DSL, version catalogs, KSP vs kapt, multi-module layout, compiler plugins | references/build-setup.md |
Output Format
When producing code:
- A short plan (1–3 bullets) of what's changing.
- The code.
- A checklist of the non-obvious MUST rules applied.
When reviewing code: call out MUST-DO / MUST-NOT violations explicitly and suggest the minimal fix.
More skills from the junie-extensions repository
View all 9 skillsandroid
build Android applications with Kotlin
Jul 13AndroidEngineeringKotlinMobilecontext7
search library and framework documentation
Jul 17DocumentationMCPReferencejava-engineer
write and refactor modern Java code
Jul 13Code AnalysisEngineeringJavalaravel-engineer
build and architect Laravel applications
Jul 13BackendEngineeringLaravelPHPphp-engineer
write and refactor modern PHP code
Jul 13Code AnalysisEngineeringPHPredis-best-practices
implement Redis best practices
Jul 13DatabaseEngineeringPerformanceRedis
More from JetBrains
View publishermps-aspect-accessories
configure JetBrains MPS module dependencies
MPS
Jul 17ArchitectureConfigurationEngineeringmps-aspect-actions
define and edit MPS node factories
MPS
Jul 17ArchitectureEngineeringmps-aspect-behavior
define and edit MPS concept behavior
MPS
Jul 13ArchitectureEngineeringmps-aspect-constraints
define JetBrains MPS language constraints
MPS
Jul 23ArchitectureCode Analysismps-aspect-dataflow
define and debug MPS dataflow builders
MPS
Jul 13Data Analysismps-aspect-editor
define MPS editor layouts
MPS
Jul 23DesignUI Components