
Kotlin Specialist Skill
- 105 installs
- 404kidwiz/claude-supercode-skills
Expert-level Kotlin development and architecture guidance for backend systems and JVM applications.
About
Kotlin specialist provides advanced expertise for Kotlin development and JVM-based systems. Essential for builders shipping production Kotlin backends and complex enterprise applications.
- Kotlin expertise
- JVM architecture
- Backend patterns
- Performance optimization
Kotlin Specialist by the numbers
- 105 all-time installs (skills.sh)
- Ranked #34 of 92 Java & JVM skills by installs in the Skillselion catalog
- Data as of Aug 11, 2026 (Skillselion catalog sync)
npx skills add https://github.com/404kidwiz/claude-supercode-skills --skill kotlin-specialistAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 105 |
|---|---|
| Repository | 404kidwiz/claude-supercode-skills ↗ |
What it does
Expert-level Kotlin development and architecture guidance for backend systems and JVM applications.
Files
Kotlin Specialist
Purpose
Provides expert Kotlin development expertise specializing in Kotlin 2.0, Kotlin Multiplatform Mobile (KMP), Coroutines, and Ktor. Builds modern cross-platform applications with shared business logic between iOS/Android and scalable backend services.
When to Use
- Building cross-platform mobile apps with shared business logic
- Developing backend services with Ktor framework
- Implementing reactive programming with Coroutines and Flow
- Modern Android development with Jetpack Compose
- Working with Kotlin 2.0 features (K2 compiler, context receivers)
- Migrating Java codebases to Kotlin
Quick Start
Invoke When
- Building KMP shared modules for iOS/Android
- Implementing Coroutines/Flow for async operations
- Creating Ktor REST APIs or WebSocket servers
- Android development with Jetpack Compose
- Migrating Java to Kotlin
Don't Invoke When
- Pure iOS development (use swift-expert)
- Flutter/React Native apps (use mobile-developer)
- Spring Boot backends (use java-architect)
- Pure JavaScript/TypeScript (use javascript-pro)
Core Capabilities
Kotlin Multiplatform
- Implementing shared business logic for iOS/Android
- Configuring KMP Gradle plugins and compiler settings
- Managing platform-specific implementations (expect/actual)
- Building cross-platform libraries and SDKs
Coroutines and Flow
- Implementing structured concurrency with Coroutines
- Building reactive streams with Flow (StateFlow, SharedFlow)
- Handling backpressure and cancellation
- Debugging coroutine execution and performance
Android Development
- Building UI with Jetpack Compose
- Implementing Architecture Components (ViewModel, Room)
- Managing dependency injection with Hilt/Koin
- Optimizing Android app performance
Backend Development
- Creating REST APIs with Ktor framework
- Implementing WebSocket connections
- Managing database access with Exposed
- Deploying Ktor applications to cloud platforms
Decision Framework
When to Choose Kotlin Multiplatform (KMP)?
Need mobile app for iOS + Android?
│
├─ YES → Shared business logic needed?
│ │
│ ├─ YES → Team has Kotlin experience?
│ │ │
│ │ ├─ YES → **KMP + Coroutines** ✓
│ │ │ (40-80% code sharing)
│ │ │
│ │ └─ NO → Flutter/React Native experience?
│ │ │
│ │ ├─ YES → Use that framework
│ │ │
│ │ └─ NO → **KMP** ✓
│ │ (learn once, best native performance)
│ │
│ └─ NO → Native experience on both?
│ │
│ ├─ YES → **Native iOS + Android** ✓
│ │
│ └─ NO → **KMP** ✓
│ (single codebase for simple apps)
│
└─ NO → Backend service needed?
└─ YES → See "Backend Framework Decision" belowBackend Framework Decision
Building backend service?
│
├─ Microservice or standalone API?
│ │
│ ├─ MICROSERVICE → Spring Boot ecosystem needed?
│ │ │
│ │ ├─ YES → **Spring Boot** ✓ (use java-architect)
│ │ │
│ │ └─ NO → Performance critical?
│ │ │
│ │ ├─ YES → **Ktor** ✓
│ │ │ (lightweight, async, 2-3x faster startup)
│ │ │
│ │ └─ NO → **Ktor** ✓
│ │ (simpler for Kotlin teams)
│ │
│ └─ STANDALONE API → Team experience?
│ │
│ ├─ Kotlin/Android → **Ktor** ✓
│ ├─ Java/Spring → **Spring Boot** ✓
│ └─ Node.js → **Node.js** ✓Coroutines vs Alternatives
| Feature | Coroutines | RxJava | Callbacks | Threads |
|---|---|---|---|---|
| Learning curve | Medium | Steep | Low | Low |
| Readability | High | Medium | Low | Low |
| Cancellation | Built-in | Manual | Manual | Manual |
| Memory overhead | ~1KB/coroutine | ~10KB/stream | Minimal | ~1MB/thread |
| Kotlin-first | Yes | No | Yes | Yes |
Recommendation: Use Coroutines + Flow for 95% of async needs.
Ktor vs Spring Boot
| Aspect | Ktor | Spring Boot |
|---|---|---|
| Startup time | 0.5-1s | 3-8s |
| Memory (idle) | 30-50MB | 150-300MB |
| Learning curve | Low (DSL-based) | Medium (annotations) |
| Ecosystem | Smaller | Massive |
| Best for | Microservices, KMP backends | Enterprise apps, monoliths |
Escalation Triggers
Red Flags → Escalate to `oracle`:
- Designing KMP architecture for apps with >10 feature modules
- Choosing between KMP and Flutter for startup MVP
- Migrating legacy Android app (Java + RxJava) to Kotlin + Coroutines
- Architecting Ktor microservices with complex distributed tracing
- Implementing custom Coroutine dispatchers or context elements
- Performance bottlenecks in Flow pipelines
Integration Patterns
mobile-developer:
- Handoff: kotlin-specialist builds KMP shared module → mobile-developer integrates into React Native/Flutter
- Collaboration: Both work on KMP project; kotlin-specialist owns shared code, mobile-developer owns platform UI
swift-expert:
- Handoff: kotlin-specialist creates KMP iOS framework → swift-expert consumes in SwiftUI app
- Tools: Kotlin/Native Cocoapods integration, Swift Package Manager
backend-developer:
- Handoff: backend-developer defines API contract → kotlin-specialist implements Ktor client in KMP
- Tools: OpenAPI/Swagger for contract-first design
database-optimizer:
- Handoff: kotlin-specialist implements Exposed queries → database-optimizer reviews for N+1 problems
- Tools: Exposed ORM, Flyway migrations
devops-engineer:
- Handoff: kotlin-specialist builds Ktor service → devops-engineer containerizes and deploys
- Tools: Ktor monitoring plugins, Prometheus metrics, Docker multi-stage builds
frontend-developer:
- Handoff: kotlin-specialist builds Ktor REST API → frontend-developer consumes from React/Vue
- Tools: OpenAPI codegen for TypeScript clients, Ktor CORS configuration
graphql-architect:
- Handoff: kotlin-specialist implements Ktor GraphQL server → graphql-architect designs schema
- Tools: graphql-kotlin-server, Apollo Kotlin for client
Additional Resources
- Detailed Technical Reference: See REFERENCE.md
- Code Examples & Patterns: See EXAMPLES.md
Kotlin Specialist - Code Examples & Patterns
This document contains code examples, anti-patterns, and real-world implementation patterns for Kotlin development.
Anti-Pattern 1: Leaking Coroutine Scope
What it looks like:
// WRONG: Leaking GlobalScope
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch { // Memory leak! Activity may be destroyed
val data = fetchData()
updateUI(data) // May crash if activity is gone
}
}
}
// WRONG: Uncontrolled lifecycleScope
class MyViewModel : ViewModel() {
fun loadData() {
// This is fine in ViewModel, but not for one-off requests
viewModelScope.launch {
while (true) { // Infinite loop, never cancelled properly
fetchAndUpdate()
delay(5000)
}
}
}
}Why it fails:
- Memory leaks: GlobalScope lives forever, holds references to destroyed activities
- Crashes: Trying to update UI after activity destruction
- Zombie coroutines: Infinite loops without cancellation checks
Correct approach:
// CORRECT: Lifecycle-aware scope
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch { // Automatically cancelled when Activity destroyed
val data = fetchData()
updateUI(data)
}
}
}
// CORRECT: Controlled polling with cancellation
class MyViewModel : ViewModel() {
private var pollingJob: Job? = null
fun startPolling() {
pollingJob?.cancel() // Cancel previous job
pollingJob = viewModelScope.launch {
while (isActive) { // Check for cancellation
fetchAndUpdate()
delay(5000)
}
}
}
fun stopPolling() {
pollingJob?.cancel()
}
override fun onCleared() {
super.onCleared()
pollingJob?.cancel() // Explicit cleanup
}
}Scope Selection Guide:
- Activity/Fragment: Use
lifecycleScope(auto-cancelled with lifecycle) - ViewModel: Use
viewModelScope(auto-cancelled when ViewModel cleared) - Application-level: Use
CoroutineScope(SupervisorJob() + Dispatchers.Main)with manual cleanup - Never: Use
GlobalScopein production code
---
Anti-Pattern 2: Blocking Main Dispatcher
What it looks like:
// WRONG: Blocking Main thread
viewModelScope.launch(Dispatchers.Main) {
val data = database.query() // Blocking I/O on Main!
val result = heavyComputation(data) // CPU-intensive on Main!
updateUI(result)
}
// WRONG: Mixing blocking and suspend
suspend fun loadData(): Data {
Thread.sleep(1000) // NEVER use Thread.sleep in suspend functions!
return fetchFromNetwork()
}Why it fails:
- ANR (Application Not Responding): UI freezes for >5 seconds
- Janky UI: Frame drops below 60fps cause stuttering
- Wasted suspend: Using
Thread.sleepblocks thread instead of suspending coroutine - Main thread starvation: Other UI updates can't run
Correct approach:
// CORRECT: Use appropriate dispatchers
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
database.query() // I/O operations on IO dispatcher
}
val result = withContext(Dispatchers.Default) {
heavyComputation(data) // CPU work on Default dispatcher
}
// Automatically back on Main dispatcher
updateUI(result)
}
// CORRECT: Use delay instead of Thread.sleep
suspend fun loadData(): Data {
delay(1000) // Suspends coroutine, doesn't block thread
return fetchFromNetwork()
}
// CORRECT: Explicit dispatcher for repository
class Repository(
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
suspend fun getData(): Data = withContext(ioDispatcher) {
database.query()
}
}---
Example: Complete KMP Shared Module
Project Structure
shared/
├── build.gradle.kts
├── src/
│ ├── commonMain/kotlin/
│ │ ├── models/
│ │ │ └── Product.kt
│ │ ├── repository/
│ │ │ └── ProductRepository.kt
│ │ └── Platform.kt
│ ├── androidMain/kotlin/
│ │ └── Platform.android.kt
│ └── iosMain/kotlin/
│ └── Platform.ios.ktCommon Module Models
// commonMain/kotlin/models/Product.kt
@Serializable
data class Product(
val id: String,
val name: String,
val price: Double,
val description: String? = null
)
@Serializable
data class ApiResponse<T>(
val success: Boolean,
val data: T?,
val error: String? = null
)Common Repository
// commonMain/kotlin/repository/ProductRepository.kt
class ProductRepository(
private val httpClient: HttpClient,
private val baseUrl: String
) {
suspend fun getProducts(): Result<List<Product>> = runCatching {
val response: ApiResponse<List<Product>> = httpClient.get("$baseUrl/products").body()
if (response.success && response.data != null) {
response.data
} else {
throw Exception(response.error ?: "Unknown error")
}
}
suspend fun getProduct(id: String): Result<Product> = runCatching {
val response: ApiResponse<Product> = httpClient.get("$baseUrl/products/$id").body()
if (response.success && response.data != null) {
response.data
} else {
throw Exception(response.error ?: "Product not found")
}
}
suspend fun createProduct(product: Product): Result<Product> = runCatching {
val response: ApiResponse<Product> = httpClient.post("$baseUrl/products") {
contentType(ContentType.Application.Json)
setBody(product)
}.body()
if (response.success && response.data != null) {
response.data
} else {
throw Exception(response.error ?: "Failed to create product")
}
}
}Platform Expect/Actual
// commonMain/kotlin/Platform.kt
expect fun getPlatformName(): String
expect fun createHttpClient(): HttpClient
// androidMain/kotlin/Platform.android.kt
actual fun getPlatformName(): String = "Android"
actual fun createHttpClient(): HttpClient = HttpClient(Android) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(Logging) {
level = LogLevel.INFO
}
}
// iosMain/kotlin/Platform.ios.kt
actual fun getPlatformName(): String = "iOS"
actual fun createHttpClient(): HttpClient = HttpClient(Darwin) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
}---
Example: Ktor REST API
// Application.kt
fun Application.module() {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
install(CallLogging) {
level = Level.INFO
filter { call -> call.request.path().startsWith("/api") }
}
install(StatusPages) {
exception<Throwable> { call, cause ->
call.application.environment.log.error("Unhandled error", cause)
call.respond(
HttpStatusCode.InternalServerError,
ErrorResponse("Internal server error")
)
}
exception<NotFoundException> { call, cause ->
call.respond(HttpStatusCode.NotFound, ErrorResponse(cause.message ?: "Not found"))
}
}
routing {
route("/api/v1") {
productRoutes()
userRoutes()
}
}
}
// ProductRoutes.kt
fun Route.productRoutes() {
val repository: ProductRepository by inject()
route("/products") {
get {
val products = repository.findAll()
call.respond(products)
}
get("/{id}") {
val id = call.parameters["id"] ?: throw BadRequestException("Missing id")
val product = repository.findById(id) ?: throw NotFoundException("Product not found")
call.respond(product)
}
post {
val request = call.receive<CreateProductRequest>()
val product = repository.create(request)
call.respond(HttpStatusCode.Created, product)
}
put("/{id}") {
val id = call.parameters["id"] ?: throw BadRequestException("Missing id")
val request = call.receive<UpdateProductRequest>()
val product = repository.update(id, request) ?: throw NotFoundException("Product not found")
call.respond(product)
}
delete("/{id}") {
val id = call.parameters["id"] ?: throw BadRequestException("Missing id")
val deleted = repository.delete(id)
if (deleted) {
call.respond(HttpStatusCode.NoContent)
} else {
throw NotFoundException("Product not found")
}
}
}
}---
Testing Patterns
Repository Testing
class ProductRepositoryTest {
private val testDispatcher = StandardTestDispatcher()
private lateinit var repository: ProductRepository
private lateinit var mockApiClient: MockApiClient
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
mockApiClient = MockApiClient()
repository = ProductRepository(mockApiClient, testDispatcher)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `loadProducts updates state to Success when API returns data`() = runTest {
// Given
val products = listOf(Product("1", "Test", 10.0, 5))
mockApiClient.setProducts(products)
// When
repository.loadProducts()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val state = repository.products.value
assertTrue(state is UiState.Success)
assertEquals(products, (state as UiState.Success).data)
}
@Test
fun `loadProducts updates state to Error when API fails`() = runTest {
// Given
mockApiClient.setError(IOException("Network error"))
// When
repository.loadProducts()
testDispatcher.scheduler.advanceUntilIdle()
// Then
val state = repository.products.value
assertTrue(state is UiState.Error)
assertEquals("Network error", (state as UiState.Error).message)
}
}Flow Testing
@Test
fun `searchProducts emits results after debounce`() = runTest {
// Given
val products = listOf(Product("1", "Test", 10.0, 5))
mockApiClient.setSearchResults(products)
// When
val results = mutableListOf<List<Product>>()
val job = launch {
repository.searchProducts("test").toList(results)
}
advanceTimeBy(300) // Wait for debounce
advanceUntilIdle()
job.cancel()
// Then
assertEquals(1, results.size)
assertEquals(products, results.first())
}---
Gradle Configuration (KMP)
// shared/build.gradle.kts
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library")
}
kotlin {
androidTarget()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("io.ktor:ktor-client-core:2.3.5")
implementation("io.ktor:ktor-client-content-negotiation:2.3.5")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.5")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:2.3.5")
}
}
val iosMain by creating {
dependsOn(commonMain)
dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.5")
}
}
}
}Kotlin Specialist - Technical Reference
This document contains detailed workflows, technical specifications, and advanced patterns for Kotlin development.
Workflow: Implement Coroutines-Based Data Layer with Flow
Goal: Build reactive repository pattern with StateFlow for UI state management.
Step 1: Define Data Models
import kotlinx.serialization.Serializable
@Serializable
data class Product(
val id: String,
val name: String,
val price: Double,
val stock: Int
)
sealed class UiState<out T> {
object Idle : UiState<Nothing>()
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}Step 2: Create Repository with Flow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class ProductRepository(
private val apiClient: ApiClient,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
private val _products = MutableStateFlow<UiState<List<Product>>>(UiState.Idle)
val products: StateFlow<UiState<List<Product>>> = _products.asStateFlow()
private val _selectedProduct = MutableStateFlow<Product?>(null)
val selectedProduct: StateFlow<Product?> = _selectedProduct.asStateFlow()
// Search products with debounce
fun searchProducts(query: String): Flow<List<Product>> = flow {
delay(300) // Debounce
val result = apiClient.searchProducts(query)
emit(result.getOrDefault(emptyList()))
}.flowOn(dispatcher)
suspend fun loadProducts() {
_products.value = UiState.Loading
withContext(dispatcher) {
apiClient.getProducts()
.onSuccess { data ->
_products.value = UiState.Success(data)
}
.onFailure { error ->
_products.value = UiState.Error(error.message ?: "Unknown error")
}
}
}
fun observeProduct(productId: String): Flow<Product?> = flow {
while (currentCoroutineContext().isActive) {
val product = apiClient.getProduct(productId).getOrNull()
emit(product)
delay(5000) // Poll every 5 seconds
}
}.flowOn(dispatcher)
fun selectProduct(product: Product) {
_selectedProduct.value = product
}
}Step 3: Create ViewModel (Android)
// Android ViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
class ProductViewModel(
private val repository: ProductRepository
) : ViewModel() {
val products: StateFlow<UiState<List<Product>>> = repository.products
val selectedProduct: StateFlow<Product?> = repository.selectedProduct
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
val searchResults: StateFlow<List<Product>> = searchQuery
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { query ->
if (query.isBlank()) flowOf(emptyList())
else repository.searchProducts(query)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
init {
loadProducts()
}
fun loadProducts() {
viewModelScope.launch {
repository.loadProducts()
}
}
fun search(query: String) {
_searchQuery.value = query
}
fun selectProduct(product: Product) {
repository.selectProduct(product)
}
}Step 4: Consume in UI (Jetpack Compose)
import androidx.compose.runtime.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@Composable
fun ProductScreen(viewModel: ProductViewModel) {
val uiState by viewModel.products.collectAsState()
val searchQuery by viewModel.searchQuery.collectAsState()
val searchResults by viewModel.searchResults.collectAsState()
Column {
TextField(
value = searchQuery,
onValueChange = { viewModel.search(it) },
placeholder = { Text("Search products...") }
)
when (uiState) {
is UiState.Idle -> Text("Pull to refresh")
is UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> {
val products = (uiState as UiState.Success).data
LazyColumn {
items(products) { product ->
ProductItem(product) { viewModel.selectProduct(product) }
}
}
}
is UiState.Error -> {
Text("Error: ${(uiState as UiState.Error).message}")
}
}
}
}Step 5: Handle Cancellation Properly
class ProductViewModel : ViewModel() {
private var pollingJob: Job? = null
fun startPolling(productId: String) {
pollingJob?.cancel() // Cancel previous polling
pollingJob = viewModelScope.launch {
repository.observeProduct(productId)
.catch { e ->
Log.e("ProductVM", "Polling error", e)
}
.collect { product ->
// Update UI with latest product data
}
}
}
override fun onCleared() {
super.onCleared()
pollingJob?.cancel() // Cleanup
}
}Expected Outcome:
- Reactive UI updates with StateFlow (single source of truth)
- Automatic debouncing and deduplication for search
- Proper lifecycle-aware coroutine scoping
- Clean separation: Repository (data) → ViewModel (business logic) → UI (presentation)
Verification:
- Search responds after 300ms pause (no network spam)
- Rotating device preserves state (ViewModel survives config changes)
- Leaving screen cancels polling job (no memory leaks)
- Check with LeakCanary and Profiler
---
Pattern: expect/actual for Platform-Specific Implementations
Use case: Access platform-specific APIs (file system, notifications, sensors) from shared code.
// commonMain/Platform.kt
expect class PlatformStorage {
suspend fun saveData(key: String, value: String)
suspend fun loadData(key: String): String?
suspend fun clearAll()
}
// androidMain/Platform.kt
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first
actual class PlatformStorage(private val context: Context) {
private val Context.dataStore by preferencesDataStore(name = "settings")
actual suspend fun saveData(key: String, value: String) {
context.dataStore.edit { prefs ->
prefs[stringPreferencesKey(key)] = value
}
}
actual suspend fun loadData(key: String): String? {
return context.dataStore.data.first()[stringPreferencesKey(key)]
}
actual suspend fun clearAll() {
context.dataStore.edit { it.clear() }
}
}
// iosMain/Platform.kt
import platform.Foundation.NSUserDefaults
actual class PlatformStorage {
private val defaults = NSUserDefaults.standardUserDefaults
actual suspend fun saveData(key: String, value: String) {
defaults.setObject(value, forKey = key)
}
actual suspend fun loadData(key: String): String? {
return defaults.stringForKey(key)
}
actual suspend fun clearAll() {
defaults.dictionaryRepresentation().keys.forEach { key ->
defaults.removeObjectForKey(key as String)
}
}
}Customization points:
- Add encryption for sensitive data (use
expect/actualfor platform crypto APIs) - Extend with type-safe keys using inline classes
- Add Flow-based observers for data changes
---
Pattern: Ktor Custom Plugin
Use case: Reusable middleware for logging, authentication, rate limiting.
// Custom request timing plugin
val RequestTimingPlugin = createApplicationPlugin(name = "RequestTiming") {
onCall { call ->
val startTime = System.currentTimeMillis()
call.response.pipeline.intercept(ApplicationSendPipeline.After) {
val duration = System.currentTimeMillis() - startTime
call.response.headers.append("X-Response-Time", "${duration}ms")
application.log.info("${call.request.uri} took ${duration}ms")
}
}
}
// Usage
fun Application.module() {
install(RequestTimingPlugin)
}
// Rate limiting plugin
data class RateLimitConfig(
val maxRequests: Int = 100,
val windowMs: Long = 60_000
)
val RateLimitPlugin = createApplicationPlugin(
name = "RateLimit",
createConfiguration = ::RateLimitConfig
) {
val requestCounts = mutableMapOf<String, MutableList<Long>>()
onCall { call ->
val clientId = call.request.headers["X-API-Key"] ?: call.request.origin.remoteHost
val now = System.currentTimeMillis()
val timestamps = requestCounts.getOrPut(clientId) { mutableListOf() }
timestamps.removeIf { it < now - pluginConfig.windowMs }
if (timestamps.size >= pluginConfig.maxRequests) {
call.respond(HttpStatusCode.TooManyRequests, "Rate limit exceeded")
finish()
} else {
timestamps.add(now)
}
}
}
// Usage with custom config
install(RateLimitPlugin) {
maxRequests = 50
windowMs = 30_000 // 30 seconds
}Customization points:
- Use Redis for distributed rate limiting
- Add IP-based vs API-key-based strategies
- Implement exponential backoff headers
---
Pattern: Structured Concurrency with supervisorScope
Use case: Run parallel tasks where one failure doesn't cancel others.
class DataSyncManager {
suspend fun syncAll(): SyncResult = supervisorScope {
val userDeferred = async { syncUsers() }
val productsDeferred = async { syncProducts() }
val ordersDeferred = async { syncOrders() }
val userResult = runCatching { userDeferred.await() }
val productResult = runCatching { productsDeferred.await() }
val orderResult = runCatching { ordersDeferred.await() }
SyncResult(
users = userResult.getOrNull(),
products = productResult.getOrNull(),
orders = orderResult.getOrNull(),
errors = listOfNotNull(
userResult.exceptionOrNull(),
productResult.exceptionOrNull(),
orderResult.exceptionOrNull()
)
)
}
private suspend fun syncUsers(): List<User> = withContext(Dispatchers.IO) {
// Sync logic
}
// ... other sync methods
}
data class SyncResult(
val users: List<User>?,
val products: List<Product>?,
val orders: List<Order>?,
val errors: List<Throwable>
) {
val isFullSuccess: Boolean get() = errors.isEmpty()
val isPartialSuccess: Boolean get() = errors.isNotEmpty() &&
(users != null || products != null || orders != null)
}Customization points:
- Use
coroutineScopeinstead if any failure should cancel all - Add retry logic with exponential backoff
- Implement progress tracking with SharedFlow
---
Dispatcher Guidelines
| Dispatcher | Use For | Thread Pool |
|---|---|---|
| Dispatchers.Main | UI updates only | Main/UI thread |
| Dispatchers.IO | Network, database, file I/O | Up to 64 threads |
| Dispatchers.Default | CPU-intensive work | Number of CPU cores |
| Dispatchers.Unconfined | Advanced use only (testing) | No thread pool |
Best Practice:
// CORRECT: Use appropriate dispatchers
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
database.query() // I/O operations on IO dispatcher
}
val result = withContext(Dispatchers.Default) {
heavyComputation(data) // CPU work on Default dispatcher
}
// Automatically back on Main dispatcher
updateUI(result)
}