--- name: kotlin-rules description: "Kotlin coding rules: style, patterns, security, testing. Triggers: .kt, .kts, build.gradle.kts, Ktor, Jetpack Compose, coroutines, kotlinx." effort: medium user-invocable: false allowed-tools: Read --- # Kotlin Rules These rules come from `app/rules/kotlin/` in ai-toolkit. They cover the project's standards for coding style, frameworks, patterns, security, and testing in Kotlin. Apply them when writing or reviewing Kotlin code. # Kotlin Coding Style ## Naming - PascalCase: classes, interfaces, objects, type aliases, enum entries. - camelCase: functions, properties, local variables, parameters. - UPPER_SNAKE: compile-time constants (`const val`), top-level `val` constants. - Backing properties: prefix with `_` (`private val _items`, `val items: List`). - Package names: lowercase, no underscores (`com.company.project.feature`). ## Null Safety - Use nullable types only when nullability is semantically meaningful. - Prefer `?.let { }`, `?:` (Elvis), and safe calls over `!!`. - Never use `!!` except in tests or when null is truly impossible. - Use `requireNotNull()` and `require()` for preconditions at public API boundaries. - Use `checkNotNull()` and `check()` for state assertions. ## Data Classes - Use `data class` for DTOs, value objects, and state containers. - Use `copy()` for immutable updates. Avoid mutable `var` in data classes. - Use `sealed class` / `sealed interface` for restricted hierarchies. - Use `value class` (inline class) for type-safe wrappers with zero overhead. - Use `object` for singletons and namespace-like utility groupings. ## Functions - Use expression body (`= expr`) for single-expression functions. - Use named arguments for functions with >2 parameters of the same type. - Use default parameter values instead of overloaded functions. - Use extension functions to add behavior without inheritance. - Use `suspend` functions for async operations, not callbacks. ## Collections - Prefer `listOf`, `mapOf`, `setOf` (immutable) over `mutableListOf`. - Use collection operators: `map`, `filter`, `groupBy`, `associate`. - Use `sequence {}` for lazy evaluation on large collections. - Prefer `firstOrNull()` over `first()` for safe access. - Use destructuring: `val (name, age) = user`. ## Scope Functions - `let`: null-safe chaining and local scoping. - `apply`: configure object after creation. - `also`: side effects (logging, validation) in chains. - `run`: compute a result using receiver's context. - `with`: multiple operations on an object without chaining. - Avoid nesting scope functions more than 1 level deep. ## Formatting - Use ktlint or detekt for automated formatting and linting. - Use trailing commas in multi-line parameter/argument lists. - Max line length: 120 characters (Kotlin convention). - Use `when` expression over if-else chains for 3+ branches. # Kotlin Frameworks ## Ktor (Server) - Use routing DSL: `routing { get("/users") { call.respond(users) } }`. - Use `install()` for plugins: ContentNegotiation, Authentication, CORS. - Use `call.receive()` for typed request body parsing with kotlinx.serialization. - Use `StatusPages` plugin for centralized error handling. - Use `Routing` with nested `route("/api/v1") { }` blocks for URL grouping. ## Ktor (Client) - Use `HttpClient` with engine configuration (CIO, OkHttp, Apache). - Use `install(ContentNegotiation) { json() }` for JSON serialization. - Use `client.get()` with reified type for typed responses. - Use `HttpTimeout` plugin for connection and request timeouts. - Close `HttpClient` when done or use DI lifecycle management. ## Spring Boot (Kotlin) - Use constructor injection (Kotlin classes are `final` by default). - Apply `kotlin-spring` plugin for open classes (required for proxying). - Use `@ConfigurationProperties` with data classes for typed config. - Use `WebFlux` with coroutines: `coRouter { }` and `suspend` handler functions. - Use `spring-boot-starter-validation` with `@Valid` on Kotlin data classes. ## Exposed (ORM) - Use DSL API for type-safe queries: `Users.select { Users.name eq "Ada" }`. - Use DAO API for Active Record-style: `User.find { Users.age greaterEq 18 }`. - Wrap database operations in `transaction { }` blocks. - Use `SchemaUtils.create(Users)` for schema management in development. ## kotlinx.serialization - Use `@Serializable` annotation on data classes for compile-time serialization. - Use `@SerialName("field_name")` for JSON field name mapping. - Use `Json { ignoreUnknownKeys = true }` for lenient deserialization. - Use polymorphic serialization with `sealed class` and `@Polymorphic`. - Prefer `kotlinx.serialization` over Jackson for pure Kotlin projects. ## Koin (DI) - Define modules: `module { single { UserService(get()) } }`. - Use `by inject()` for lazy injection in Android/Ktor. - Use `factory { }` for new instance per injection, `single { }` for singleton. - Use `checkModules()` in tests to verify DI graph completeness. ## Compose (Multiplatform UI) - Use `@Composable` functions for UI components. Keep them stateless. - Use `remember { }` and `mutableStateOf()` for local state. - Hoist state to callers: pass state down, events up. - Use `LaunchedEffect` for side effects tied to composition lifecycle. - Use `ViewModel` with `StateFlow` for screen-level state management. # Kotlin Patterns ## Error Handling - Use `Result` for operations that can fail without exceptions. - Use `runCatching { }` to wrap exception-throwing code into `Result`. - Use `sealed class` hierarchies for domain errors: `sealed class AppError`. - Prefer `fold()`, `getOrElse()`, `getOrNull()` over `getOrThrow()`. - Use `require()` / `check()` for preconditions; they throw `IllegalArgumentException` / `IllegalStateException`. ## Coroutines - Use `suspend` functions for sequential async operations. - Use `coroutineScope { }` for structured concurrency with parallel work. - Use `async { }` + `await()` for concurrent independent operations. - Use `supervisorScope { }` when child failures should not cancel siblings. - Use `withContext(Dispatchers.IO)` for blocking I/O in coroutine context. - Use `flow { }` for cold asynchronous streams. Collect in lifecycle-aware scope. ## Flow Patterns - Use `stateIn()` and `shareIn()` to convert cold flows to hot shared state. - Use `combine()` to merge multiple flows into derived state. - Use `flatMapLatest` for search-as-you-type patterns (cancel previous). - Use `catch { }` operator for upstream error handling in flows. - Use `flowOn(Dispatchers.IO)` to shift upstream execution context. ## Sealed Hierarchies - Use `sealed interface` over `sealed class` when no shared state is needed. - Use `when` expressions exhaustively on sealed types (compiler-enforced). - Combine sealed types with data classes for typed state machines. - Use sealed hierarchies for API responses: `Success`, `Error`, `Loading`. ## Delegation - Use `by lazy { }` for thread-safe lazy initialization. - Use `by map` for delegated properties backed by a `Map`. - Use class delegation (`class Foo : Bar by impl`) to favor composition. - Use `observable` / `vetoable` delegates for reactive property changes. ## Builder Patterns - Use DSL-style builders with `@DslMarker` annotation to prevent scope leakage. - Use trailing lambda syntax for configuration blocks. - Use `apply { }` for inline object configuration without a dedicated builder. - Use `buildList { }`, `buildMap { }`, `buildString { }` for collection construction. ## Anti-Patterns - Overusing `!!`: masks null-safety guarantees. Use safe calls or require. - Nesting scope functions: `foo.let { it.also { ... }.run { } }` -- flatten logic. - Blocking the main thread: use `withContext(Dispatchers.IO)` for I/O. - Using `GlobalScope.launch`: leaks coroutines. Use structured concurrency. - Mutable shared state without synchronization: use `Mutex` or `StateFlow`. # Kotlin Security ## Input Validation - Validate all inputs at API boundaries using Bean Validation or manual checks. - Use `require()` for argument validation: `require(age > 0) { "Age must be positive" }`. - Use data class `init` blocks for domain validation on construction. - Never trust client-provided IDs. Verify resource ownership server-side. - Sanitize strings before using in HTML, SQL, or shell commands. ## Null Safety as Security - Kotlin's null safety prevents null pointer exceptions. Do not circumvent with `!!`. - Use `?.` and `?:` chains for safe fallback values at boundaries. - Treat Java interop as untrusted: platform types can still be null. - Use `@Nullable` / `@NotNull` annotations on Java code consumed by Kotlin. ## SQL Injection - Use Exposed DSL or JPA with parameterized queries. Never concatenate input. - Use `PreparedStatement` if writing raw JDBC. - Use `CriteriaBuilder` or Exposed conditions for dynamic query construction. - Audit `@Query(nativeQuery = true)` for parameter interpolation risks. ## Serialization - Use `kotlinx.serialization` with `@Serializable` for compile-time safety. - Use `Json { ignoreUnknownKeys = true }` but validate after deserialization. - Never use Java `ObjectInputStream` for deserialization (RCE risk). - Restrict polymorphic deserialization to known sealed class subtypes. ## Authentication - Use Spring Security or Ktor Authentication plugin. Do not roll your own. - Hash passwords with BCrypt or Argon2. Never store plaintext. - Use short-lived JWTs (15 min) with refresh token rotation. - Validate JWT signature, issuer, audience, and expiration on every request. ## Coroutine Security - Use `withTimeout()` to prevent unbounded coroutine execution (DoS vector). - Use `Mutex` for critical sections. Do not use `synchronized` in suspend functions. - Propagate security context through `CoroutineContext` elements. - Cancel coroutine scopes on authentication failure or session expiry. ## Secrets Management - Use environment variables or Vault for secrets. Never hardcode. - Use `@ConfigurationProperties` with injected secrets, not string literals. - Never log request headers containing Authorization tokens. - Use separate configuration profiles for dev/staging/prod secrets. ## Dependencies - Use Dependabot or Renovate for automated dependency updates. - Run OWASP Dependency-Check or Gradle `dependencyCheckAnalyze`. - Audit transitive dependencies with `gradle dependencies`. - Pin dependency versions. Avoid dynamic versions like `1.+`. ## Logging - Use parameterized logging: `logger.info("User {} logged in", userId)`. - Never log passwords, tokens, or PII. - Sanitize user input before logging to prevent log injection. - Use structured logging (JSON) for machine-parseable audit trails. # Kotlin Testing ## Framework - Use JUnit 5 as the test runner. - Use Kotest for Kotlin-idiomatic BDD-style testing (alternative). - Use MockK for mocking (Kotlin-native, supports coroutines). - Use Testcontainers for integration tests with external services. ## File Naming - Test files: `FooTest.kt` in `src/test/kotlin/` mirroring source package. - Integration tests: `FooIT.kt` or use `@Tag("integration")`. - Use `@Nested` inner classes to group related test cases. ## Structure - Use `@DisplayName` for human-readable test names. - Use backtick function names for readable test names: `` `returns 404 when user not found` ``. - Use `@BeforeEach` for per-test setup. Avoid shared mutable state. - Use `@ParameterizedTest` with `@MethodSource` for table-driven tests. ## MockK - Use `mockk()` to create mocks. - Use `every { mock.find(any()) } returns user` for stubbing. - Use `coEvery { ... }` and `coVerify { ... }` for coroutine mocking. - Use `spyk()` for partial mocks on real objects. - Use `slot()` and `captured` to inspect arguments. - Clear mocks in `@AfterEach` to prevent state leakage. ## Coroutine Testing - Use `runTest { }` from `kotlinx-coroutines-test` for coroutine tests. - Use `TestDispatcher` to control coroutine execution timing. - Use `advanceUntilIdle()` to complete all pending coroutines. - Use `turbine` library for testing `Flow` emissions. ## Assertions - Use AssertJ or Kotest assertions for fluent, readable checks. - Use `shouldBe`, `shouldThrow`, `shouldContain` (Kotest matchers). - Use `assertSoftly { }` to collect multiple assertion failures. - Use `assertThrows { ... }` for exception testing. ## Test Data - Use factory functions for test data: `fun aUser(name: String = "Ada") = User(...)`. - Use default parameters for minimal test data setup. - Use `copy()` on data classes for variations of base test objects. - Use `faker` library for realistic test data generation. ## Spring Integration - Use `@SpringBootTest` with `@AutoConfigureMockMvc` for API tests. - Use `@WebMvcTest` for controller-only tests (faster). - Use `@MockkBean` instead of `@MockBean` for MockK integration. - Use `@Transactional` on test classes for automatic rollback.