Install
$ agentstack add skill-b33eep-claude-code-setup-standards-kotlin ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Kotlin Coding Standards
Core Principles
- Explicitness: Explicit code over implicit magic
- Readability: Readable code over clever tricks
- Null Safety: Embrace Kotlin's null safety system
- Immutability: Prefer
valovervar, immutable collections - Expressiveness: Use Kotlin's expressive features (data classes, sealed classes)
- DRY: Don't Repeat Yourself - but keep it simple
General Rules
- Prefer
valovervar: 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 | UPPERSNAKECASE | 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.
// 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.
// 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.
// 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:
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xcontext-receivers")
}
}
Usage:
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:
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xexplicit-backing-fields")
}
}
Before:
private val _users = MutableStateFlow>(emptyList())
val users: StateFlow> = _users
After:
val users: StateFlow> field = MutableStateFlow(emptyList())
UUID API (Experimental, 2.3)
Built-in UUID support without external dependencies.
Enable with:
@OptIn(ExperimentalUuidApi::class)
Usage:
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.
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.
// 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
// 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
// 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
// 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
// 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
// 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
- Single source of truth: Expose
StateFlow/SharedFlow, keepMutableStateFlow/MutableSharedFlowprivate - Use
collectLatestfor UI: Cancel previous work on new emissions - Use
stateInfor cold-to-hot conversion:
val data: StateFlow = dataRepository.getData()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = Data.Empty
)
Null Safety
Safe Calls, Elvis, and !! Operator
// 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
// 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
// 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.
// 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
// 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
// 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
// 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
// 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.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.