Stack conventions
Kotlin
Kotlin on the JVM (services and libraries) with coroutines, built with Gradle Kotlin DSL and tested with JUnit 5 or kotlin.test.
Kotlin on the JVM (services and libraries) with coroutines, built with Gradle Kotlin DSL and tested with JUnit 5 or kotlin.test.
Used for: kotlin.
Principles
- Use the type system for null safety: non-null types by default,
?only where absence is meaningful; no!!except where a null is a proven programming error. - Prefer
valovervarand read-only collection types (List,Map) in APIs; expose mutable state only inside the owning class. - Model data with
data class, closed hierarchies withsealedclasses/interfaces, and handle them with exhaustivewhenexpressions (noelsebranch). - Use structured concurrency: launch coroutines in a scope tied to a lifecycle; never use
GlobalScope;suspendfunctions are main-safe and do not block. - Inject dispatchers (or use the framework's) instead of hard-coding
Dispatchers.IOinside business logic, so tests can control them. - Never swallow
CancellationException: rethrow it when catching broad exceptions in suspend code. - Write idiomatic Kotlin, not Java in Kotlin syntax: expression bodies, named and default arguments instead of overloads/builders, scope functions used sparingly and readably.
- Use
require/check/errorfor preconditions and invariants; return sealed results or nullable types for expected failures. - Keep extension functions discoverable and focused; do not use them to reach into another module's internals.
Conventions
Accessibility
Concurrency
- Every coroutine has an owner scope; cancel the scope when the owner ends.
- Use
withContext(dispatcher)for blocking calls; never call blocking I/O onDispatchers.Defaultor the main thread. - Protect shared mutable state with
Mutex, atomic types or confinement; do not usesynchronizedaround suspension points.
Dependencies
- Declare versions in the Gradle version catalog (
gradle/libs.versions.toml) and use the Kotlin Gradle DSL (build.gradle.kts). - Keep the Kotlin compiler, coroutines and serialization library versions compatible.
Documentation
- KDoc on public API with
@param/@return/@throwswhere they add information; Markdown in KDoc for examples. - Comment why, not what; keep the README's build, run and configuration steps current when they change.
Errors
- Throw exceptions for programming errors; model expected failures with sealed types or
Resultat boundaries. - Use
runCatchingonly where you then handle both outcomes, and never around suspend calls without rethrowingCancellationException.
Logging
- Use the repo's logger (SLF4J, kotlin-logging) with lazy or parameterised messages; never log secrets or personal data.
- Log structured key-value context (request id, entity id) at boundaries; keep DEBUG noise out of hot paths.
Naming
- Classes and objects in UpperCamelCase; functions, properties and locals in lowerCamelCase;
const valand top-level immutable constants in UPPER_SNAKE_CASE. - Backing properties for exposed read-only state use a leading underscore (
private val _state/val state). - File names match the single class they contain, or describe the contents for top-level functions (
StringExtensions.kt).
Performance
- Prefer sequences (
asSequence()) for long chains over large collections; plain collection operations for small ones. - Avoid unnecessary allocations in hot paths (lambdas capturing state, boxing of nullable primitives); measure before optimising.
Security
- Parameterise all database queries (Exposed DSL, JPA parameters, jOOQ bindings); never interpolate input into SQL strings.
- Validate external input at the boundary and convert it to domain types (value classes) once.
- Keep secrets in environment variables or a secret store.
- Never pass external input to
Runtime.exec,ProcessBuilderwith a shell, or script engines (injection). - Lock dependency versions (Gradle dependency locking or a verified version catalog) and scan them for known vulnerabilities.
Structure
- Package structure follows directories; omit the common root package directory as the conventions allow when the repo does.
- Order class contents: properties and init blocks, secondary constructors, methods, companion object last.
- Keep visibility minimal (
private/internal); enable explicit API mode for libraries where the repo uses it.
Testing
- Use JUnit 5 or kotlin.test with the repo's assertion library (AssertK, Kotest, AssertJ); MockK for Kotlin-friendly mocking where mocks are needed.
- Test coroutines with
runTestand aTestDispatcher; inject dispatchers so tests do not depend on real time. - Test Flows with Turbine or
toListon a bounded flow. - Unit-test classes in isolation with fakes; integration tests (Testcontainers, Ktor
testApplication, Spring slices) cover database and HTTP boundaries. - Name tests after the behaviour with backticks (
fun `rejects an order without lines`()), one behaviour per test. - Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Are nullable types used only where absence is meaningful, without unjustified
!!? - Are
valand read-only collections used wherever mutation is not required? - Are sealed hierarchies handled with exhaustive
whenexpressions? - Does every coroutine run in a scope tied to an owner, with no GlobalScope?
- Is CancellationException rethrown wherever broad exceptions are caught in suspend code?
- Are blocking calls moved off shared dispatchers with
withContext? - Are dispatchers injectable for testing?
- Are database queries parameterised?
- Are coroutine tests written with
runTestand test dispatchers? - Is the code idiomatic Kotlin (named/default arguments, data classes) rather than transliterated Java?
- Are public APIs documented with KDoc where behaviour is not obvious?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
detekt |
static | detekt | no |
ktlint |
lint | ktlint (Gradle plugin) | no |
spotless-gradle |
format | spotless | no |
gradle-check |
test | gradle check | no |
osv-scanner |
security | osv-scanner | no |
gitleaks |
security | gitleaks | no |