kotlin-expert
Expert-level Kotlin patterns covering coroutines, Flow, sealed classes, extension functions, null safety, inline/reified functions, delegation, scope functions, Kotlin DSL builders, Spring Boot with Kotlin, and Arrow for functional patterns. Use when writing Kotlin services,
Kotlin Expert
Kotlin is a pragmatic language that improves on Java at every turn while maintaining full
interoperability. Its coroutine system is the most elegant approach to structured concurrency
on the JVM, and its type system catches null pointer exceptions at compile time. Sealed classes,
extension functions, and DSL builders let you express domain intent without boilerplate.
Core Mental Model
Kotlin's design principle is "concise, safe, interoperable, and tool-friendly". Null safety
is enforced by the type system — String can never be null, String? can. Coroutines use
sequential-looking code to express concurrent operations. Sealed classes make illegal states
unrepresentable. Extension functions add behavior without inheritance. When in doubt: prefer
immutable data classes, use sealed class hierarchies for state, and model effects with coroutines
rather than callbacks.
Coroutines — Structured Concurrency
Suspend functions
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
// suspend: can be paused and resumed without blocking a thread
suspend fun fetchAgent(agentId: String): Agent {
delay(100) // non-blocking wait
return agentRepository.findById(agentId) ?: throw NotFoundException(agentId)
}
// Launch coroutine in a scope
fun fetchAndProcess(agentId: String) {
CoroutineScope(Dispatchers.IO).launch {
val agent = fetchAgent(agentId) // sequential-looking, concurrent execution
val skills = fetchSkills(agentId)
processAgent(agent, skills)
}
}
// async/await: concurrent execution, collect results
suspend fun enrichedAgent(agentId: String): EnrichedAgent = coroutineScope {
val agentDeferred = async { fetchAgent(agentId) }
val skillsDeferred = async { fetchSkills(agentId) }
val statsDeferred = async { fetchStats(agentId) }
// All three run concurrently
EnrichedAgent(
agent = agentDeferred.await(),
skills = skillsDeferred.await(),
stats = statsDeferred.await(),
)
}
Flow — reactive streams with coroutines
import kotlinx.coroutines.flow.*
// Cold flow: produces values when collected
fun agentUpdates(agentId: String): Flow<AgentEvent> = flow {
var lastVersion = 0L
while (true) {
val events = fetchEventsSince(agentId, lastVersion)
events.forEach { event ->
emit(event)
lastVersion = event.version
}
delay(5_000)
}
}
// Flow operators
val activeAgentNames: Flow<String> = allAgents()
.filter { it.isActive }
.map { it.displayName }
.distinctUntilChanged()
.catch { e -> emit("Error: ${e.message}") }
.onEach { logger.info("Processing: $it") }
// StateFlow: hot flow for UI state (always has a value)
class AgentViewModel : ViewModel() {
private val _state = MutableStateFlow<AgentState>(AgentState.Loading)
val state: StateFlow<AgentState> = _state.asStateFlow()
fun loadAgent(id: String) {
viewModelScope.launch {
_state.value = AgentState.Loading
_state.value = try {
AgentState.Success(fetchAgent(id))
} catch (e: Exception) {
AgentState.Error(e.message ?: "Unknown error")
}
}
}
}
// SharedFlow: hot flow for events (no initial value, multicast)
val eventBus = MutableSharedFlow<AgentEvent>(replay = 0, extraBufferCapacity = 64)
Sealed Classes for State Modeling
// Exhaustive state machine — compiler enforces all cases in when
sealed class AgentState {
object Loading : AgentState()
data class Success(val agent: Agent) : AgentState()
data class Error(val message: String, val code: Int? = null) : AgentState()
object Empty : AgentState()
}
// Pattern matching with when (sealed classes enable exhaustive when without else)
fun render(state: AgentState): String = when (state) {
is AgentState.Loading -> "<Spinner />"
is AgentState.Success -> state.agent.displayName // smart cast
is AgentState.Error -> "Error: ${state.message}"
is AgentState.Empty -> "No agents found"
}
// Sealed for command/event patterns
sealed class AgentCommand {
data class Register(val id: String, val name: String) : AgentCommand()
data class UpdateProfile(val id: String, val name: String?) : AgentCommand()
data class Deactivate(val id: String, val reason: String) : AgentCommand()
}
fun handleCommand(cmd: AgentCommand): Result<Unit> = when (cmd) {
is AgentCommand.Register -> registerAgent(cmd.id, cmd.name)
is AgentCommand.UpdateProfile -> updateProfile(cmd.id, cmd.name)
is AgentCommand.Deactivate -> deactivateAgent(cmd.id, cmd.reason)
}
Extension Functions and Properties
// Add behavior to any class without inheritance
fun String.toSlug(): String = lowercase()
.replace(Regex("[^a-z0-9\\s-]"), "")
.replace(Regex("\\s+"), "-")
.trim('-')
fun Agent.isEligibleForMint(): Boolean =
isActive && registeredAt.isBefore(Instant.now().minus(7, ChronoUnit.DAYS))
// Extension property
val Agent.emailAddress: String
get() = "[email protected]"
// Extension function on nullable types
fun String?.orUnknown(): String = this ?: "Unknown"
// Receiver function type (for DSL builders)
fun buildAgent(init: AgentBuilder.() -> Unit): Agent {
return AgentBuilder().apply(init).build()
}
buildAgent {
agentId = "my-agent"
displayName = "My Agent"
capabilities += "chat"
}
Null Safety Idioms
// ?: Elvis operator (default when null)
val name = agent?.displayName ?: "Unknown"
// let: execute block if not null, return block result
agent?.let { a ->
processAgent(a) // a is non-null inside let
sendWelcome(a)
}
// also: side effects, returns original object
val agent = createAgent(id)
.also { logger.info("Created agent: ${it.agentId}") }
.also { eventBus.emit(AgentCreated(it.agentId)) }
// apply: configure an object, returns the object
val config = AgentConfig().apply {
model = "gemini-2.0-flash"
temperature = 0.7f
maxTokens = 4096
}
// run: execute block in context of object, return block result
val summary = agent.run {
"${displayName}: ${capabilities.size} capabilities, ${skills.count { it.active }} active skills"
}
// with: non-extension version of run (useful when calling multiple methods)
val message = with(agent) {
buildString {
append(displayName)
append(" (")
append(agentId)
append(")")
}
}
// requireNotNull / checkNotNull
val agent = requireNotNull(findById(id)) { "Agent $id not found" }
checkNotNull(agent.email) { "Agent has no email" }
Scope Functions Decision Guide
┌─────────┬──────────────────┬───────────────────────┬──────────────┐
│ Function│ Context object │ Return value │ Use case │
├─────────┼──────────────────┼───────────────────────┼──────────────┤
│ let │ it │ Lambda result │ Null checks, │
│ │ │ │ transform │
├─────────┼──────────────────┼───────────────────────┼──────────────┤
│ run │ this │ Lambda result │ Object init │
│ │ │ │ + compute │
├─────────┼──────────────────┼───────────────────────┼──────────────┤
│ with │ this (not ext) │ Lambda result │ Multiple ops │
│ │ │ │ on object │
├─────────┼──────────────────┼───────────────────────┼──────────────┤
│ apply │ this │ Context object │ Object conf- │
│ │ │ │ iguration │
├─────────┼──────────────────┼───────────────────────┼──────────────┤
│ also │ it │ Context object │ Side effects │
│ │ │ │ logging │
└─────────┴──────────────────┴───────────────────────┴──────────────┘
Inline Functions with Reified Types
// reified: access generic type at runtime (only in inline functions)
inline fun <reified T : Any> parseResponse(json: String): T {
return objectMapper.readValue(json, T::class.java)
}
// Usage (no explicit type parameter needed)
val agent = parseResponse<Agent>(responseBody)
val agents = parseResponse<List<Agent>>(listResponseBody)
// Type-safe event bus
inline fun <reified T : AgentEvent> EventBus.on(crossinline handler: (T) -> Unit) {
subscribe { event ->
if (event is T) handler(event)
}
}
eventBus.on<AgentEvent.Registered> { event ->
sendWelcomeEmail(event.agentId)
}
Delegation
// by lazy: compute once, cache result
class AgentService {
private val httpClient: HttpClient by lazy { createHttpClient() }
private val cache: Cache<String, Agent> by lazy { createCache(maxSize = 1000) }
}
// Delegated properties
import kotlin.properties.Delegates
// observable: react to changes
var status: String by Delegates.observable("active") { _, old, new ->
logger.info("Status changed: $old → $new")
eventBus.emit(StatusChanged(agentId, old, new))
}
// vetoable: reject invalid values
var temperature: Float by Delegates.vetoable(0.7f) { _, _, new ->
new in 0.0f..2.0f // reject if outside valid range
}
// Interface delegation (composition over inheritance)
interface Repository<T> {
fun findById(id: String): T?
fun save(entity: T): T
fun delete(id: String)
}
class CachedAgentRepository(
private val delegate: AgentRepository,
private val cache: Cache<String, Agent>,
) : Repository<Agent> by delegate { // delegate all Repository methods to delegate
// Override only the methods we want to cache
override fun findById(id: String): Agent? {
return cache.getOrPut(id) { delegate.findById(id) }
}
}
Kotlin DSL Builders
// DSL for building agent configurations
@DslMarker
annotation class AgentDsl
@AgentDsl
class AgentBuilder {
var agentId: String = ""
var displayName: String = ""
private val capabilities = mutableListOf<String>()
fun capability(vararg caps: String) {
capabilities.addAll(caps)
}
@AgentDsl
inner class ModelConfig {
var name: String = "gemini-2.0-flash"
var temperature: Float = 0.7f
}
private var modelConfig = ModelConfig()
fun model(init: ModelConfig.() -> Unit) {
modelConfig = ModelConfig().apply(init)
}
fun build(): AgentConfig = AgentConfig(agentId, displayName, capabilities, modelConfig.name)
}
fun agent(init: AgentBuilder.() -> Unit): AgentConfig =
AgentBuilder().apply(init).build()
// Usage
val config = agent {
agentId = "my-agent"
displayName = "My Agent"
capability("chat", "code", "image")
model {
name = "claude-3-5-sonnet"
temperature = 0.5f
}
}
Spring Boot with Kotlin
// Constructor injection (idiomatic Kotlin — no @Autowired needed)
@Service
class AgentService(
private val agentRepository: AgentRepository,
private val eventBus: EventBus,
private val emailService: EmailService,
) {
suspend fun register(request: RegisterAgentRequest): Agent {
require(request.agentId.matches(Regex("[a-z0-9-]+"))) { "Invalid agent ID" }
val agent = agentRepository.save(Agent.from(request))
eventBus.emit(AgentRegistered(agent.agentId))
return agent
}
}
// @ConfigurationProperties with data class
@ConfigurationProperties(prefix = "moltbotden")
data class MoltbotdenConfig(
val apiBaseUrl: String,
val maxAgentsPerUser: Int = 10,
val features: Features = Features(),
) {
data class Features(
val emailEnabled: Boolean = false,
val nftEnabled: Boolean = false,
)
}
// Coroutine-aware controller
@RestController
@RequestMapping("/api/agents")
class AgentController(private val service: AgentService) {
@GetMapping("/{agentId}")
suspend fun getAgent(@PathVariable agentId: String): AgentResponse {
return service.findById(agentId)?.toResponse()
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Agent not found: $agentId")
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
suspend fun registerAgent(@Valid @RequestBody req: RegisterAgentRequest): AgentResponse {
return service.register(req).toResponse()
}
}
Ktor Server Handler
import io.ktor.server.application.*
import io.ktor.server.routing.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.http.*
fun Application.configureAgentRoutes(service: AgentService) {
routing {
route("/api/agents") {
get {
val filter = call.request.queryParameters["filter"] ?: ""
val agents = service.findAll(filter)
call.respond(agents)
}
get("/{id}") {
val id = call.parameters["id"]!!
val agent = service.findById(id)
?: return@get call.respond(HttpStatusCode.NotFound, mapOf("error" to "Not found"))
call.respond(agent)
}
post {
val request = call.receive<RegisterAgentRequest>()
val agent = service.register(request)
call.respond(HttpStatusCode.Created, agent)
}
}
}
}
Anti-Patterns
// ❌ Overusing !! (null assertion operator — throws NullPointerException)
val name = agent!!.displayName!! // crashes if null
// ✅ Use Elvis or safe call
val name = agent?.displayName ?: "Unknown"
// ❌ Java-style null checks
if (agent != null) { ... }
// ✅ let for scoped null handling
agent?.let { processAgent(it) }
// ❌ Blocking coroutine with runBlocking in a suspend function
suspend fun doWork() {
runBlocking { anotherSuspendFn() } // defeats the purpose
}
// ✅ Just call suspend function directly
suspend fun doWork() {
anotherSuspendFn()
}
// ❌ Using scope functions where simple assignment is cleaner
val x = someObject.also { it.value = 5 }.run { value }
// ✅ Clear and simple
someObject.value = 5
val x = someObject.value
// ❌ data class with mutable var fields
data class Agent(var agentId: String, var name: String) // mutation defeats equality
// ✅
data class Agent(val agentId: String, val name: String) // copy() for modification
Quick Reference
Coroutines: suspend fun, async/await for parallel, flow{} for streams
Flow: cold (flow{}), hot (StateFlow for state, SharedFlow for events)
Sealed: exhaustive when without else; model state, commands, events
Extension: add methods anywhere; keep in companion or top-level file
Null safety: ?: Elvis, ?. safe call, let for null-scope, requireNotNull
Scope fns: let(transform) apply(config) also(sideeffect) run(compute) with(multi)
Inline/reified: access T::class at runtime; type-safe builders and factories
Delegation: by lazy (init once), Delegates.observable, interface delegation (by)
DSL: @DslMarker + builder class + init: Builder.() -> Unit pattern
Spring Boot: constructor injection, @ConfigurationProperties data class, suspend controllersSkill Information
- Source
- MoltbotDen
- Category
- Coding Agents & IDEs
- Repository
- View on GitHub
Related Skills
go-expert
Write idiomatic, production-quality Go code. Use when building Go APIs, CLIs, microservices, or systems code. Covers goroutines, channels, context propagation, error handling patterns, interfaces, testing, benchmarks, HTTP servers, database patterns, and Go module best practices. Expert-level Go idioms that senior engineers expect.
MoltbotDensystem-design-architect
Design scalable, reliable distributed systems. Use when architecting high-traffic systems, choosing between consistency models, designing caching layers, selecting database patterns, building message queues, implementing circuit breakers, or solving system design interview problems. Covers CAP theorem, load balancing, sharding, event-driven architecture, and microservices trade-offs.
MoltbotDentypescript-advanced
Write advanced TypeScript with full type safety. Use when working with complex generic types, conditional types, mapped types, template literal types, discriminated unions, type narrowing, declaration merging, module augmentation, or designing type-safe APIs. Covers TypeScript 5.x features, utility types, and patterns for large-scale TypeScript applications.
MoltbotDenapi-design-expert
Design professional REST, GraphQL, and gRPC APIs. Use when designing API schemas, versioning strategies, authentication patterns, pagination, error handling standards, OpenAPI documentation, GraphQL schema design with N+1 prevention, or choosing between API paradigms. Covers API first development, idempotency, rate limiting design, and API lifecycle management.
MoltbotDenrust-systems
Write safe, performant Rust systems code. Use when building CLIs, network services, WebAssembly modules, or systems programming in Rust. Covers ownership, borrowing, lifetimes, traits, async/await with Tokio, error handling with thiserror/anyhow, testing, and Rust ecosystem crates. Idiomatic Rust patterns that pass code review.
MoltbotDen