Use when you have a written implementation plan to execute in a separate session with review checkpoints
0
0
# Install this skill:
npx skills add iceflower/opencode-agents-and-skills --skill "kotlin-convention"
Install specific skill from multi-skill repository
# Description
Kotlin coding conventions and idioms. Use when writing or reviewing
# SKILL.md
name: kotlin-convention
description: Kotlin coding conventions and idioms. Use when writing or reviewing
Kotlin code.
Kotlin Convention Rules
1. Null Safety
Preferred Patterns
// Use safe call + Elvis for default values
val name = user?.name ?: "Unknown"
// Use requireNotNull for preconditions (throws IllegalArgumentException)
val userId = requireNotNull(request.userId) { "userId must not be null" }
// Use checkNotNull for state validation (throws IllegalStateException)
val session = checkNotNull(currentSession) { "Session not initialized" }
// Use let for null-conditional execution
user?.let { sendNotification(it) }
Null Anti-Patterns
// Never use !! in production code
val name = user!!.name // Bad: crashes with NPE
// Avoid unnecessary null types
fun getUser(): User? // Bad if it never returns null
fun getUser(): User // Good: non-null return when guaranteed
2. Data Class vs Class
| Use | When |
|---|---|
data class |
DTOs, request/response objects, value objects, config properties |
class |
Services, repositories, entities with mutable state, classes with complex behavior |
value class |
Single-field wrappers for type safety (IDs, amounts) |
sealed class |
Restricted type hierarchies (states, results, error types) |
object |
Singletons, utility collections, companion factories |
// Value class for type-safe IDs
@JvmInline
value class UserId(val value: Long)
// Sealed class for result types
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Failure(val error: ErrorCode) : Result<Nothing>()
}
3. Extension Functions
Good Use Cases
// Converting between layers (Entity β DTO)
fun User.toResponse() = UserResponse(id = id, name = name, email = email)
// Adding domain-specific utility
fun String.toSlug() = lowercase().replace(Regex("[^a-z0-9]+"), "-").trim('-')
// Scoping to specific contexts
fun RestClient.ResponseSpec.orThrow(message: String): RestClient.ResponseSpec =
onStatus(HttpStatusCode::isError) { _, _ -> throw ExternalApiException(message) }
Extension Anti-Patterns
- Extension functions that access private/internal state of the receiver
- Extension functions that modify mutable state
- Overusing extensions where a regular method on the class is more appropriate
4. Collection Operations
Prefer Kotlin Standard Library
// Transforming
val names = users.map { it.name }
val activeUsers = users.filter { it.isActive }
val userMap = users.associateBy { it.id }
// Aggregating
val totalAmount = orders.sumOf { it.amount }
val grouped = orders.groupBy { it.status }
// Null-safe collections
val firstActive = users.firstOrNull { it.isActive }
val nicknames = users.mapNotNull { it.nickname }
Performance Considerations
- Use
asSequence()for chains of 3+ operations on large collections - Use
buildList/buildMapfor constructing collections conditionally - Avoid
flatMapinside loops β prefer restructuring
5. Coroutines (for async APIs)
Structured Concurrency
// Use coroutineScope for parallel operations
suspend fun fetchDashboard(userId: Long): Dashboard = coroutineScope {
val profile = async { userService.getProfile(userId) }
val orders = async { orderService.getRecentOrders(userId) }
Dashboard(profile.await(), orders.await())
}
Spring Integration
- Use
suspend funin controllers for WebFlux/reactive endpoints - Use
@AsyncwithCompletableFuturefor MVC-based projects - Never use
GlobalScopeβ always use structured concurrency - Use
Dispatchers.IOfor blocking I/O operations
6. Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Class | PascalCase | UserService, OrderResponse |
| Function | camelCase | findByEmail, calculateTotal |
| Property | camelCase | userName, isActive |
| Constant | SCREAMING_SNAKE | MAX_RETRY_COUNT, DEFAULT_PAGE_SIZE |
| Package | lowercase dot-separated | com.example.app |
| Enum value | SCREAMING_SNAKE | PENDING, IN_PROGRESS |
7. Spring Boot Specific
Constructor Injection (preferred)
@Service
class UserService(
private val userRepository: UserRepository,
private val eventPublisher: ApplicationEventPublisher
) {
// No @Autowired needed β single constructor auto-wired
}
Configuration Properties
@ConfigurationProperties(prefix = "app.feature")
data class FeatureProperties(
val enabled: Boolean = false,
val maxRetries: Int = 3,
val timeout: Duration = Duration.ofSeconds(30)
)
Spring Boot Anti-Patterns
@Autowiredfield injection β use constructor injectionlateinit varfor injected dependencies β use constructor parametersopenclasses/methods for Spring proxying β useallopenplugin instead
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Amp
Antigravity
Claude Code
Clawdbot
Codex
Cursor
Droid
Gemini CLI
GitHub Copilot
Goose
Kilo Code
Kiro CLI
OpenCode
Roo Code
Trae
Windsurf
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.