---
name: best-practices-9b0a9ce7ca09
description: Kotlin Coroutines with Spring Boot/WebFlux performance optimization and best practices guidelines
license: MIT
metadata:
  author: devnogari
  project_type: kotlin-spring
  rule_count: 48
  categories: 8
---

# Kotlin Coroutines + Spring Boot Best Practices

Comprehensive performance optimization and best practices for Spring Boot applications with Kotlin Coroutines. **48 rules across 8 categories** based on Spring Framework 7.0, Spring Boot 4.0, Kotlin 2.x coroutines, and R2DBC async patterns.

## Activation Triggers

This skill activates when:
- Writing new controllers, services, or repositories with coroutines
- Implementing Flow patterns and reactive streams
- Optimizing async performance with WebFlux
- Reviewing or refactoring Kotlin coroutine code
- Debugging scope, cancellation, or flow issues
- Implementing authentication/authorization with reactive security
- Writing tests for coroutine-based applications

## Rule Categories

| # | Category | Priority | Prefix | Rule Count | Focus |
|---|----------|----------|--------|------------|-------|
| 1 | Coroutine Basics | CRITICAL | `coroutine-` | 6 | suspend functions, dispatchers |
| 2 | Scope Management | CRITICAL | `scope-` | 6 | GlobalScope avoidance, structured concurrency |
| 3 | Flow Patterns | HIGH | `flow-` | 6 | Flow, StateFlow, SharedFlow |
| 4 | R2DBC Database | HIGH | `db-` | 6 | Reactive repositories, transactions |
| 5 | Error Handling | HIGH | `err-` | 6 | SupervisorJob, exception handling |
| 6 | Spring WebFlux | MEDIUM | `webflux-` | 6 | Reactive endpoints, context propagation |
| 7 | Security | MEDIUM | `sec-` | 6 | Reactive security, JWT, context |
| 8 | Testing | MEDIUM | `test-` | 6 | runTest, MockK, Turbine |

## Quick Reference - All 48 Rules

### Coroutine Basics (CRITICAL) - `coroutine-*`

| Rule ID | Rule Name | Impact |
|---------|-----------|--------|
| `coroutine-suspend` | Use suspend functions correctly | Non-blocking execution |
| `coroutine-dispatcher` | Choose correct dispatcher | CPU/IO optimization |
| `coroutine-blocking` | Never block in coroutines | Prevents thread starvation |
| `coroutine-context` | Understand CoroutineContext | Proper context propagation |
| `coroutine-async` | Use async/await for parallel | Concurrent execution |
| `coroutine-withContext` | Use withContext for dispatcher switch | Clean context switching |

### Scope Management (CRITICAL) - `scope-*`

| Rule ID | Rule Name | Impact |
|---------|-----------|--------|
| `scope-no-global` | Never use GlobalScope | Prevents memory leaks |
| `scope-structured` | Use structured concurrency | Automatic cleanup |
| `scope-coroutineScope` | Use coroutineScope for grouping | Child coroutine management |
| `scope-supervisorScope` | Use supervisorScope for isolation | Fault tolerance |
| `scope-lifecycle` | Tie scope to lifecycle | Resource management |
| `scope-injection` | Inject CoroutineScope | Testability |

### Flow Patterns (HIGH) - `flow-*`

| Rule ID | Rule Name | Impact |
|---------|-----------|--------|
| `flow-cold` | Understand Flow is cold | Proper subscription |
| `flow-operators` | Use flow operators correctly | Efficient transformations |
| `flow-stateflow` | Use StateFlow for state | UI state management |
| `flow-sharedflow` | Use SharedFlow for events | Event broadcasting |
| `flow-flowOn` | Use flowOn for context | Single context switch |
| `flow-collect` | Collect safely in scope | Prevent leaks |

### R2DBC Database (HIGH) - `db-*`

| Rule ID | Rule Name | Impact |
|---------|-----------|--------|
| `db-r2dbc` | Use R2DBC not JDBC | Non-blocking DB |
| `db-repository` | Use CoroutineCrudRepository | Coroutine-native API |
| `db-transaction` | Handle transactions correctly | Data consistency |
| `db-connection-pool` | Configure connection pool | Resource efficiency |
| `db-n-plus-one` | Avoid N+1 queries | Query optimization |
| `db-migration` | Use Flyway/Liquibase async | Schema management |

### Error Handling (HIGH) - `err-*`

| Rule ID | Rule Name | Impact |
|---------|-----------|--------|
| `err-supervisor` | Use SupervisorJob correctly | Fault isolation |
| `err-handler` | Use CoroutineExceptionHandler | Centralized error handling |
| `err-try-catch` | Catch exceptions in coroutines | Proper error recovery |
| `err-cancellation` | Handle CancellationException | Correct cancellation |
| `err-flow-catch` | Use catch operator in Flow | Flow error handling |
| `err-retry` | Implement retry with backoff | Resilience |

### Spring WebFlux (MEDIUM) - `webflux-*`

| Rule ID | Rule Name | Impact |
|---------|-----------|--------|
| `webflux-controller` | Use suspend in controllers | Non-blocking endpoints |
| `webflux-coRouter` | Use coRouter DSL | Coroutine-friendly routing |
| `webflux-webclient` | Use WebClient with awaitBody | Async HTTP calls |
| `webflux-context` | Propagate ReactorContext | Observability, tracing |
| `webflux-response` | Return proper types | Efficient serialization |
| `webflux-filter` | Use CoWebFilter | Coroutine-aware filters |

### Security (MEDIUM) - `sec-*`

| Rule ID | Rule Name | Impact |
|---------|-----------|--------|
| `sec-reactive` | Use ReactiveSecurityContextHolder | Reactive auth context |
| `sec-method` | Use @PreAuthorize with suspend | Method-level security |
| `sec-context-propagation` | Propagate SecurityContext | Cross-thread auth |
| `sec-jwt` | Implement JWT properly | Stateless auth |
| `sec-cors` | Configure CORS correctly | Cross-origin security |
| `sec-csrf` | Handle CSRF in WebFlux | Request forgery protection |

### Testing (MEDIUM) - `test-*`

| Rule ID | Rule Name | Impact |
|---------|-----------|--------|
| `test-runTest` | Use runTest for coroutines | Proper test execution |
| `test-mockk` | Use MockK coEvery/coVerify | Suspend function mocking |
| `test-turbine` | Use Turbine for Flow testing | Flow assertions |
| `test-dispatcher` | Use TestDispatcher | Deterministic tests |
| `test-webclient` | Test with WebTestClient | Integration testing |
| `test-container` | Use Testcontainers | Database testing |

---

## Detailed Rules by Category

### 1. Coroutine Basics (CRITICAL)

#### `coroutine-suspend` - Use Suspend Functions Correctly

**Impact**: Non-blocking execution, proper coroutine integration

```kotlin
// ❌ INCORRECT - blocking call in suspend function
suspend fun getUser(id: Long): User {
    Thread.sleep(1000)  // BLOCKS the thread!
    return repository.findById(id)  // blocking JDBC
}

// ✅ CORRECT - proper suspend function
suspend fun getUser(id: Long): User {
    delay(1000)  // non-blocking delay
    return repository.findByIdOrNull(id)  // R2DBC suspend
        ?: throw UserNotFoundException(id)
}

// ✅ CORRECT - marking function as suspend when it calls other suspend functions
suspend fun getUserWithOrders(userId: Long): UserWithOrders {
    val user = userRepository.findById(userId)  // suspend call
    val orders = orderRepository.findByUserId(userId)  // suspend call
    return UserWithOrders(user, orders)
}
```

#### `coroutine-dispatcher` - Choose Correct Dispatcher

**Impact**: Optimized thread utilization for CPU vs IO operations

```kotlin
// ❌ INCORRECT - CPU-intensive work on Default/IO dispatcher
suspend fun processImage(data: ByteArray): ByteArray {
    return withContext(Dispatchers.IO) {  // Wrong! IO is for blocking I/O
        heavyImageProcessing(data)  // CPU-bound work
    }
}

// ✅ CORRECT - use appropriate dispatchers
suspend fun processImage(data: ByteArray): ByteArray {
    return withContext(Dispatchers.Default) {  // CPU-bound work
        heavyImageProcessing(data)
    }
}

suspend fun readFile(path: String): String {
    return withContext(Dispatchers.IO) {  // Blocking I/O
        File(path).readText()
    }
}

// ✅ CORRECT - no dispatcher needed for suspend-native operations
suspend fun fetchFromApi(url: String): Response {
    // WebClient is already non-blocking, no withContext needed
    return webClient.get()
        .uri(url)
        .awaitExchange { it.awaitBody() }
}
```

#### `coroutine-blocking` - Never Block in Coroutines

**Impact**: Prevents thread starvation, maintains concurrency

```kotlin
// ❌ INCORRECT - blocking operations in coroutine
@GetMapping("/data")
suspend fun getData(): Data {
    val result = blockingHttpClient.get(url)  // BLOCKS!
    Thread.sleep(100)  // BLOCKS!
    return result
}

// ✅ CORRECT - use non-blocking alternatives
@GetMapping("/data")
suspend fun getData(): Data {
    val result = webClient.get()
        .uri(url)
        .awaitBody<Data>()  // non-blocking
    delay(100)  // non-blocking
    return result
}

// ✅ CORRECT - if blocking is unavoidable, use withContext(Dispatchers.IO)
suspend fun legacyOperation(): Result {
    return withContext(Dispatchers.IO) {
        legacyBlockingClient.execute()  // isolated on IO dispatcher
    }
}
```

#### `coroutine-context` - Understand CoroutineContext

**Impact**: Proper context propagation for logging, tracing, auth

```kotlin
// ❌ INCORRECT - losing context elements
suspend fun processWithLogging() {
    // MDC context lost when switching dispatchers
    withContext(Dispatchers.Default) {
        logger.info("Processing")  // MDC context may be empty!
    }
}

// ✅ CORRECT - propagate context elements
suspend fun processWithLogging() {
    val mdcContext = MDCContext()
    withContext(Dispatchers.Default + mdcContext) {
        logger.info("Processing")  // MDC context preserved
    }
}

// ✅ CORRECT - Spring Boot 4.0 automatic context propagation
// application.properties: spring.reactor.context-propagation=auto
@GetMapping("/trace")
suspend fun tracedEndpoint(): Response {
    // Tracing context automatically propagated
    return service.process()
}
```

#### `coroutine-async` - Use async/await for Parallel Execution

**Impact**: Concurrent execution, reduced latency

```kotlin
// ❌ INCORRECT - sequential execution (3x latency)
suspend fun getDashboard(userId: Long): Dashboard {
    val user = userService.getUser(userId)        // 100ms
    val orders = orderService.getOrders(userId)   // 100ms
    val notifications = notificationService.get() // 100ms
    return Dashboard(user, orders, notifications) // Total: 300ms
}

// ✅ CORRECT - parallel execution with coroutineScope
suspend fun getDashboard(userId: Long): Dashboard = coroutineScope {
    val user = async { userService.getUser(userId) }
    val orders = async { orderService.getOrders(userId) }
    val notifications = async { notificationService.get() }

    Dashboard(
        user = user.await(),
        orders = orders.await(),
        notifications = notifications.await()
    )  // Total: ~100ms (max of all)
}

// ✅ CORRECT - with structured error handling
suspend fun getDashboardSafe(userId: Long): Dashboard = supervisorScope {
    val user = async { userService.getUser(userId) }
    val orders = async {
        runCatching { orderService.getOrders(userId) }
            .getOrDefault(emptyList())
    }
    val notifications = async {
        runCatching { notificationService.get() }
            .getOrDefault(emptyList())
    }

    Dashboard(user.await(), orders.await(), notifications.await())
}
```

#### `coroutine-withContext` - Use withContext for Dispatcher Switch

**Impact**: Clean context switching without nested scopes

```kotlin
// ❌ INCORRECT - unnecessary scope creation
suspend fun processData(data: Data): Result {
    return CoroutineScope(Dispatchers.Default).async {
        heavyProcessing(data)
    }.await()  // Creates new scope, loses parent context!
}

// ✅ CORRECT - use withContext
suspend fun processData(data: Data): Result {
    return withContext(Dispatchers.Default) {
        heavyProcessing(data)
    }  // Maintains parent scope, proper cancellation
}

// ✅ CORRECT - combine context elements
suspend fun processWithTimeout(data: Data): Result {
    return withContext(Dispatchers.Default + CoroutineName("processor")) {
        withTimeout(5000) {
            heavyProcessing(data)
        }
    }
}
```

---

### 2. Scope Management (CRITICAL)

#### `scope-no-global` - Never Use GlobalScope

**Impact**: Prevents memory leaks, enables proper cancellation

```kotlin
// ❌ INCORRECT - GlobalScope leaks and can't be cancelled
@Service
class NotificationService {
    fun sendAsync(notification: Notification) {
        GlobalScope.launch {  // NEVER CANCELLED!
            sendNotification(notification)
        }
    }
}

// ✅ CORRECT - inject scope tied to service lifecycle
@Service
class NotificationService(
    private val scope: CoroutineScope  // injected
) {
    fun sendAsync(notification: Notification) {
        scope.launch {  // cancelled when service destroyed
            sendNotification(notification)
        }
    }
}

// ✅ CORRECT - define scope as Spring bean
@Configuration
class CoroutineConfig {
    @Bean
    fun applicationScope(): CoroutineScope {
        return CoroutineScope(
            SupervisorJob() +
            Dispatchers.Default +
            CoroutineName("app-scope")
        )
    }

    @Bean
    fun cleanupScope(scope: CoroutineScope): DisposableBean {
        return DisposableBean { scope.cancel() }
    }
}
```

#### `scope-structured` - Use Structured Concurrency

**Impact**: Automatic cleanup, proper parent-child relationships

```kotlin
// ❌ INCORRECT - fire-and-forget without structure
suspend fun processOrders(orders: List<Order>) {
    orders.forEach { order ->
        CoroutineScope(Dispatchers.Default).launch {
            processOrder(order)  // orphaned coroutines!
        }
    }
    // returns immediately, children still running
}

// ✅ CORRECT - structured concurrency waits for children
suspend fun processOrders(orders: List<Order>) = coroutineScope {
    orders.forEach { order ->
        launch {
            processOrder(order)
        }
    }
    // waits for all children to complete
}

// ✅ CORRECT - parallel with result collection
suspend fun processOrders(orders: List<Order>): List<Result> = coroutineScope {
    orders.map { order ->
        async {
            processOrder(order)
        }
    }.awaitAll()
}
```

#### `scope-coroutineScope` - Use coroutineScope for Grouping

**Impact**: Child coroutine management, exception propagation

```kotlin
// ✅ CORRECT - group related operations
suspend fun updateUserProfile(userId: Long, profile: Profile): User = coroutineScope {
    // Validate in parallel
    val validationDeferred = async { validateProfile(profile) }
    val existingUser = async { userRepository.findById(userId) }

    validationDeferred.await()  // throws if invalid
    val user = existingUser.await() ?: throw UserNotFoundException(userId)

    // Update with validated data
    val updatedUser = user.copy(
        name = profile.name,
        email = profile.email
    )

    // Parallel side effects
    launch { auditService.logUpdate(userId) }
    launch { cacheService.invalidate(userId) }

    userRepository.save(updatedUser)
}
```

#### `scope-supervisorScope` - Use supervisorScope for Isolation

**Impact**: Fault tolerance, independent child failures

```kotlin
// ❌ INCORRECT - one failure cancels all
suspend fun sendNotifications(users: List<User>) = coroutineScope {
    users.forEach { user ->
        launch {
            sendNotification(user)  // one failure cancels ALL
        }
    }
}

// ✅ CORRECT - isolated failures with supervisorScope
suspend fun sendNotifications(users: List<User>) = supervisorScope {
    users.forEach { user ->
        launch {
            try {
                sendNotification(user)
            } catch (e: Exception) {
                logger.error("Failed to notify ${user.id}", e)
                // continues with other users
            }
        }
    }
}

// ✅ CORRECT - collect results with partial failure handling
suspend fun fetchAllData(): AggregatedData = supervisorScope {
    val users = async {
        runCatching { userService.getAll() }.getOrDefault(emptyList())
    }
    val products = async {
        runCatching { productService.getAll() }.getOrDefault(emptyList())
    }
    val orders = async {
        runCatching { orderService.getAll() }.getOrDefault(emptyList())
    }

    AggregatedData(users.await(), products.await(), orders.await())
}
```

#### `scope-lifecycle` - Tie Scope to Lifecycle

**Impact**: Proper resource management, no orphaned coroutines

```kotlin
// ✅ CORRECT - scope tied to component lifecycle
@Component
class BackgroundProcessor : DisposableBean {
    private val scope = CoroutineScope(
        SupervisorJob() + Dispatchers.Default
    )

    fun startProcessing() {
        scope.launch {
            while (isActive) {
                processNextItem()
                delay(1000)
            }
        }
    }

    override fun destroy() {
        scope.cancel()  // cleanup on shutdown
    }
}

// ✅ CORRECT - request-scoped with Spring
@RestController
class UserController {
    @GetMapping("/users/{id}")
    suspend fun getUser(@PathVariable id: Long): User {
        // Spring creates request-scoped coroutine context
        return userService.getUser(id)
    }
}
```

#### `scope-injection` - Inject CoroutineScope for Testability

**Impact**: Easy testing, flexible dispatcher configuration

```kotlin
// ✅ CORRECT - inject scope for testability
@Service
class OrderProcessor(
    private val orderRepository: OrderRepository,
    private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) {
    fun processAsync(orderId: Long): Job {
        return scope.launch {
            val order = orderRepository.findById(orderId)
            processOrder(order)
        }
    }
}

// In tests
@Test
fun `test order processing`() = runTest {
    val testScope = this  // TestScope from runTest
    val processor = OrderProcessor(mockRepository, testScope)

    processor.processAsync(1L)
    advanceUntilIdle()

    coVerify { mockRepository.findById(1L) }
}
```

---

### 3. Flow Patterns (HIGH)

#### `flow-cold` - Understand Flow is Cold

**Impact**: Proper subscription behavior, no unexpected side effects

```kotlin
// ❌ INCORRECT - expecting Flow to run immediately
fun getUpdates(): Flow<Update> = flow {
    println("Starting flow")  // Not printed until collected!
    emit(fetchUpdate())
}

// This doesn't start the flow:
val updates = getUpdates()  // Nothing happens yet

// ✅ CORRECT - collect to start the flow
suspend fun processUpdates() {
    getUpdates().collect { update ->
        println("Received: $update")
    }
}

// ✅ CORRECT - use SharedFlow for hot streams
@Service
class EventService {
    private val _events = MutableSharedFlow<Event>(
        replay = 1,
        extraBufferCapacity = 64
    )
    val events: SharedFlow<Event> = _events.asSharedFlow()

    suspend fun emit(event: Event) {
        _events.emit(event)
    }
}
```

#### `flow-operators` - Use Flow Operators Correctly

**Impact**: Efficient transformations, proper backpressure handling

```kotlin
// ✅ CORRECT - chain operators efficiently
fun getProcessedOrders(): Flow<ProcessedOrder> {
    return orderRepository.findAllAsFlow()
        .filter { it.status == OrderStatus.PENDING }
        .map { order -> processOrder(order) }
        .catch { e ->
            logger.error("Processing failed", e)
            emit(ProcessedOrder.failed())
        }
        .onCompletion { logger.info("Processing complete") }
}

// ✅ CORRECT - use buffer for slow collectors
fun getNotifications(): Flow<Notification> {
    return notificationSource.asFlow()
        .buffer(capacity = 64)  // buffer if collector is slow
        .map { enrichNotification(it) }
}

// ✅ CORRECT - use conflate for latest-only semantics
fun getPriceUpdates(): Flow<Price> {
    return priceSource.asFlow()
        .conflate()  // keep only latest if collector is slow
}

// ✅ CORRECT - debounce for rate limiting
fun getSearchResults(queries: Flow<String>): Flow<List<Result>> {
    return queries
        .debounce(300)  // wait 300ms of inactivity
        .distinctUntilChanged()
        .flatMapLatest { query ->
            searchService.search(query)
        }
}
```

#### `flow-stateflow` - Use StateFlow for State

**Impact**: Proper state management, UI updates

```kotlin
// ✅ CORRECT - StateFlow for observable state
@Service
class ConnectionService {
    private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)
    val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()

    suspend fun connect() {
        _connectionState.value = ConnectionState.CONNECTING
        try {
            establishConnection()
            _connectionState.value = ConnectionState.CONNECTED
        } catch (e: Exception) {
            _connectionState.value = ConnectionState.ERROR
        }
    }
}

// ✅ CORRECT - expose immutable StateFlow
class ViewModel {
    private val _uiState = MutableStateFlow(UiState.Initial)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    // Never expose MutableStateFlow directly
}
```

#### `flow-sharedflow` - Use SharedFlow for Events

**Impact**: Event broadcasting, multiple subscribers

```kotlin
// ✅ CORRECT - SharedFlow for one-time events
@Service
class EventBus {
    private val _events = MutableSharedFlow<AppEvent>(
        extraBufferCapacity = 64,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val events: SharedFlow<AppEvent> = _events.asSharedFlow()

    suspend fun publish(event: AppEvent) {
        _events.emit(event)
    }

    // For fire-and-forget without suspend
    fun tryPublish(event: AppEvent): Boolean {
        return _events.tryEmit(event)
    }
}

// ✅ CORRECT - replay for late subscribers
@Service
class ConfigService {
    private val _config = MutableSharedFlow<Config>(
        replay = 1  // late subscribers get last value
    )
    val config: SharedFlow<Config> = _config.asSharedFlow()
}
```

#### `flow-flowOn` - Use flowOn for Context Switching

**Impact**: Single context switch point, efficient execution

```kotlin
// ❌ INCORRECT - multiple unnecessary context switches
fun processItems(): Flow<Item> = flow {
    items.forEach { item ->
        val processed = withContext(Dispatchers.Default) {  // switch per item!
            process(item)
        }
        emit(processed)
    }
}

// ✅ CORRECT - single flowOn for upstream
fun processItems(): Flow<Item> = flow {
    items.forEach { item ->
        emit(process(item))
    }
}.flowOn(Dispatchers.Default)  // all upstream on Default

// ✅ CORRECT - multiple flowOn for different stages
fun loadAndProcessData(): Flow<Result> {
    return loadFromDb()
        .flowOn(Dispatchers.IO)  // DB operations on IO
        .map { process(it) }
        .flowOn(Dispatchers.Default)  // processing on Default
}
```

#### `flow-collect` - Collect Safely in Scope

**Impact**: Prevent leaks, proper cancellation

```kotlin
// ❌ INCORRECT - collecting in GlobalScope
@Service
class EventListener {
    fun startListening() {
        GlobalScope.launch {  // leaks!
            events.collect { handleEvent(it) }
        }
    }
}

// ✅ CORRECT - collect in lifecycle-bound scope
@Service
class EventListener(
    private val scope: CoroutineScope
) : DisposableBean {

    private var job: Job? = null

    fun startListening() {
        job = scope.launch {
            events.collect { handleEvent(it) }
        }
    }

    override fun destroy() {
        job?.cancel()
    }
}

// ✅ CORRECT - use launchIn for cleaner syntax
@Service
class EventListener(
    private val scope: CoroutineScope
) {
    init {
        events
            .onEach { handleEvent(it) }
            .launchIn(scope)
    }
}
```

---

### 4. R2DBC Database (HIGH)

#### `db-r2dbc` - Use R2DBC Not JDBC

**Impact**: Non-blocking database operations

```kotlin
// ❌ INCORRECT - blocking JDBC
@Repository
class UserRepository(
    private val jdbcTemplate: JdbcTemplate
) {
    fun findById(id: Long): User? {
        return jdbcTemplate.queryForObject(...)  // BLOCKS!
    }
}

// ✅ CORRECT - R2DBC with coroutines
@Repository
interface UserRepository : CoroutineCrudRepository<User, Long> {
    suspend fun findByEmail(email: String): User?

    @Query("SELECT * FROM users WHERE status = :status")
    fun findByStatus(status: String): Flow<User>
}

// ✅ CORRECT - configuration
@Configuration
@EnableR2dbcRepositories
class DatabaseConfig : AbstractR2dbcConfiguration() {

    @Bean
    override fun connectionFactory(): ConnectionFactory {
        return ConnectionFactories.get(
            ConnectionFactoryOptions.builder()
                .option(DRIVER, "postgresql")
                .option(HOST, "localhost")
                .option(DATABASE, "mydb")
                .option(USER, "user")
                .option(PASSWORD, "pass")
                .build()
        )
    }
}
```

#### `db-repository` - Use CoroutineCrudRepository

**Impact**: Native coroutine API, clean suspend functions

```kotlin
// ✅ CORRECT - CoroutineCrudRepository for suspend functions
@Repository
interface OrderRepository : CoroutineCrudRepository<Order, Long> {

    // Suspend function for single result
    suspend fun findByOrderNumber(orderNumber: String): Order?

    // Flow for multiple results
    fun findByUserId(userId: Long): Flow<Order>

    // Custom query with suspend
    @Query("SELECT * FROM orders WHERE status = :status LIMIT :limit")
    suspend fun findTopByStatus(status: String, limit: Int): List<Order>
}

// ✅ CORRECT - use in service
@Service
class OrderService(
    private val orderRepository: OrderRepository
) {
    suspend fun getOrder(id: Long): Order {
        return orderRepository.findById(id)
            ?: throw OrderNotFoundException(id)
    }

    fun getUserOrders(userId: Long): Flow<Order> {
        return orderRepository.findByUserId(userId)
    }
}
```

#### `db-transaction` - Handle Transactions Correctly

**Impact**: Data consistency, ACID compliance

```kotlin
// ❌ INCORRECT - no transaction for multiple operations
suspend fun transferMoney(from: Long, to: Long, amount: BigDecimal) {
    val fromAccount = accountRepository.findById(from)!!
    fromAccount.balance -= amount
    accountRepository.save(fromAccount)  // committed

    val toAccount = accountRepository.findById(to)!!  // if this fails, money lost!
    toAccount.balance += amount
    accountRepository.save(toAccount)
}

// ✅ CORRECT - programmatic transaction with coroutines
@Service
class TransferService(
    private val transactionalOperator: TransactionalOperator,
    private val accountRepository: AccountRepository
) {
    suspend fun transferMoney(from: Long, to: Long, amount: BigDecimal) {
        transactionalOperator.executeAndAwait {
            val fromAccount = accountRepository.findById(from)
                ?: throw AccountNotFoundException(from)
            val toAccount = accountRepository.findById(to)
                ?: throw AccountNotFoundException(to)

            if (fromAccount.balance < amount) {
                throw InsufficientFundsException()
            }

            accountRepository.save(fromAccount.copy(balance = fromAccount.balance - amount))
            accountRepository.save(toAccount.copy(balance = toAccount.balance + amount))
        }
    }
}

// ✅ CORRECT - @Transactional with suspend
@Service
class OrderService(
    private val orderRepository: OrderRepository,
    private val inventoryRepository: InventoryRepository
) {
    @Transactional
    suspend fun createOrder(order: Order): Order {
        val savedOrder = orderRepository.save(order)
        inventoryRepository.decrementStock(order.productId, order.quantity)
        return savedOrder
    }
}
```

#### `db-connection-pool` - Configure Connection Pool

**Impact**: Resource efficiency, connection reuse

```kotlin
// ✅ CORRECT - R2DBC pool configuration
@Configuration
class R2dbcConfig {

    @Bean
    fun connectionFactory(): ConnectionFactory {
        val connectionFactory = ConnectionFactories.get(
            ConnectionFactoryOptions.builder()
                .option(DRIVER, "pool")
                .option(PROTOCOL, "postgresql")
                .option(HOST, "localhost")
                .option(DATABASE, "mydb")
                .option(USER, System.getenv("DB_USER"))
                .option(PASSWORD, System.getenv("DB_PASSWORD"))
                .build()
        )

        val poolConfig = ConnectionPoolConfiguration.builder(connectionFactory)
            .maxIdleTime(Duration.ofMinutes(10))
            .maxSize(20)
            .initialSize(5)
            .maxCreateConnectionTime(Duration.ofSeconds(5))
            .validationQuery("SELECT 1")
            .build()

        return ConnectionPool(poolConfig)
    }
}

// application.yml alternative
# spring:
#   r2dbc:
#     url: r2dbc:pool:postgresql://localhost/mydb
#     pool:
#       initial-size: 5
#       max-size: 20
#       max-idle-time: 10m
```

#### `db-n-plus-one` - Avoid N+1 Queries

**Impact**: Query optimization, reduced database load

```kotlin
// ❌ INCORRECT - N+1 queries
suspend fun getUsersWithOrders(): List<UserWithOrders> {
    val users = userRepository.findAll().toList()
    return users.map { user ->
        val orders = orderRepository.findByUserId(user.id).toList()  // N queries!
        UserWithOrders(user, orders)
    }
}

// ✅ CORRECT - batch fetch with join or single query
@Repository
interface UserRepository : CoroutineCrudRepository<User, Long> {

    @Query("""
        SELECT u.*, o.* FROM users u
        LEFT JOIN orders o ON u.id = o.user_id
        WHERE u.status = 'ACTIVE'
    """)
    fun findActiveUsersWithOrders(): Flow<UserWithOrders>
}

// ✅ CORRECT - manual batching
suspend fun getUsersWithOrders(): List<UserWithOrders> {
    val users = userRepository.findAll().toList()
    val userIds = users.map { it.id }

    // Single batch query for all orders
    val ordersByUser = orderRepository.findByUserIdIn(userIds)
        .toList()
        .groupBy { it.userId }

    return users.map { user ->
        UserWithOrders(user, ordersByUser[user.id] ?: emptyList())
    }
}
```

#### `db-migration` - Use Flyway/Liquibase

**Impact**: Schema version control, safe migrations

```kotlin
// ✅ CORRECT - Flyway configuration for R2DBC
// build.gradle.kts
dependencies {
    implementation("org.flywaydb:flyway-core")
    runtimeOnly("org.postgresql:postgresql")  // for migrations
}

// application.yml
# spring:
#   flyway:
#     enabled: true
#     locations: classpath:db/migration
#     url: jdbc:postgresql://localhost/mydb  # JDBC for Flyway
#     user: ${DB_USER}
#     password: ${DB_PASSWORD}
#   r2dbc:
#     url: r2dbc:postgresql://localhost/mydb  # R2DBC for app

// ✅ CORRECT - migration script V1__create_users.sql
/*
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);
*/
```

---

### 5. Error Handling (HIGH)

#### `err-supervisor` - Use SupervisorJob Correctly

**Impact**: Fault isolation, independent failure handling

```kotlin
// ❌ INCORRECT - SupervisorJob without exception handler
val scope = CoroutineScope(SupervisorJob())

scope.launch {
    throw Exception("Unhandled!")  // crashes the app on Android, logged on server
}

// ✅ CORRECT - SupervisorJob with CoroutineExceptionHandler
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    logger.error("Coroutine failed", exception)
    errorReporter.report(exception)
}

val scope = CoroutineScope(
    SupervisorJob() +
    Dispatchers.Default +
    exceptionHandler
)

// ✅ CORRECT - handle exceptions in each child
scope.launch {
    try {
        riskyOperation()
    } catch (e: Exception) {
        logger.error("Operation failed", e)
        // handle gracefully
    }
}
```

#### `err-handler` - Use CoroutineExceptionHandler

**Impact**: Centralized error handling for uncaught exceptions

```kotlin
// ✅ CORRECT - global exception handler
@Configuration
class CoroutineExceptionConfig {

    @Bean
    fun coroutineExceptionHandler(): CoroutineExceptionHandler {
        return CoroutineExceptionHandler { context, exception ->
            val name = context[CoroutineName]?.name ?: "unknown"
            logger.error("Coroutine '$name' failed", exception)

            when (exception) {
                is CancellationException -> {
                    // Normal cancellation, don't report
                }
                is NetworkException -> {
                    // Retry logic or user notification
                    retryQueue.add(context)
                }
                else -> {
                    errorReporter.report(exception)
                }
            }
        }
    }

    @Bean
    fun applicationScope(handler: CoroutineExceptionHandler): CoroutineScope {
        return CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
    }
}
```

#### `err-try-catch` - Catch Exceptions in Coroutines

**Impact**: Proper error recovery, graceful degradation

```kotlin
// ❌ INCORRECT - unhandled exception in async
suspend fun fetchData(): Data = coroutineScope {
    val result = async {
        riskyNetworkCall()  // exception not caught by try-catch below
    }

    try {
        result.await()
    } catch (e: Exception) {
        // This catches, but coroutineScope already cancelled!
        defaultData()
    }
}

// ✅ CORRECT - catch at the source
suspend fun fetchData(): Data = coroutineScope {
    val result = async {
        try {
            riskyNetworkCall()
        } catch (e: Exception) {
            logger.warn("Network call failed, using default", e)
            defaultData()
        }
    }
    result.await()
}

// ✅ CORRECT - use supervisorScope for independent failures
suspend fun fetchData(): Data = supervisorScope {
    val result = async {
        riskyNetworkCall()
    }

    try {
        result.await()
    } catch (e: Exception) {
        defaultData()
    }
}

// ✅ CORRECT - use runCatching
suspend fun fetchDataSafe(): Result<Data> {
    return runCatching {
        riskyNetworkCall()
    }
}
```

#### `err-cancellation` - Handle CancellationException Properly

**Impact**: Correct cancellation propagation, resource cleanup

```kotlin
// ❌ INCORRECT - swallowing CancellationException
suspend fun processItems(items: List<Item>) {
    items.forEach { item ->
        try {
            processItem(item)
        } catch (e: Exception) {  // catches CancellationException!
            logger.error("Failed", e)
            // Cancellation is swallowed, coroutine continues!
        }
    }
}

// ✅ CORRECT - rethrow CancellationException
suspend fun processItems(items: List<Item>) {
    items.forEach { item ->
        try {
            processItem(item)
        } catch (e: CancellationException) {
            throw e  // always rethrow!
        } catch (e: Exception) {
            logger.error("Failed to process ${item.id}", e)
        }
    }
}

// ✅ CORRECT - use runCatching which handles this
suspend fun processItems(items: List<Item>) {
    items.forEach { item ->
        runCatching {
            processItem(item)
        }.onFailure { e ->
            if (e is CancellationException) throw e
            logger.error("Failed to process ${item.id}", e)
        }
    }
}

// ✅ CORRECT - cleanup with try-finally
suspend fun processWithResource() {
    val resource = acquireResource()
    try {
        process(resource)
    } finally {
        withContext(NonCancellable) {
            resource.close()  // cleanup even on cancellation
        }
    }
}
```

#### `err-flow-catch` - Use catch Operator in Flow

**Impact**: Flow error handling without breaking collection

```kotlin
// ❌ INCORRECT - exception breaks entire flow
fun getUpdates(): Flow<Update> = flow {
    while (true) {
        val update = fetchUpdate()  // throws on network error
        emit(update)
        delay(1000)
    }
}

// ✅ CORRECT - catch and recover
fun getUpdates(): Flow<Update> = flow {
    while (true) {
        val update = fetchUpdate()
        emit(update)
        delay(1000)
    }
}.catch { e ->
    logger.error("Update fetch failed", e)
    emit(Update.error(e.message))  // emit error state
}

// ✅ CORRECT - retry on error
fun getUpdates(): Flow<Update> = flow {
    val update = fetchUpdate()
    emit(update)
}.retry(3) { e ->
    e is NetworkException  // retry only network errors
}.catch { e ->
    emit(Update.error("Failed after retries"))
}

// ✅ CORRECT - onEach for side effects, catch for errors
fun processNotifications(): Flow<Notification> {
    return notificationSource
        .onEach { notification ->
            analyticsService.track(notification)
        }
        .catch { e ->
            logger.error("Notification processing failed", e)
            // don't emit, just log
        }
}
```

#### `err-retry` - Implement Retry with Backoff

**Impact**: Resilience for transient failures

```kotlin
// ✅ CORRECT - retry with exponential backoff
suspend fun <T> retryWithBackoff(
    maxRetries: Int = 3,
    initialDelay: Long = 100,
    maxDelay: Long = 1000,
    factor: Double = 2.0,
    block: suspend () -> T
): T {
    var currentDelay = initialDelay
    repeat(maxRetries - 1) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            if (e is CancellationException) throw e
            logger.warn("Attempt ${attempt + 1} failed, retrying in ${currentDelay}ms", e)
        }
        delay(currentDelay)
        currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
    }
    return block()  // last attempt, let it throw
}

// Usage
suspend fun fetchDataWithRetry(): Data {
    return retryWithBackoff(maxRetries = 3) {
        apiClient.fetchData()
    }
}

// ✅ CORRECT - retry in Flow
fun getDataStream(): Flow<Data> = flow {
    while (currentCoroutineContext().isActive) {
        emit(fetchData())
        delay(5000)
    }
}.retryWhen { cause, attempt ->
    if (cause is NetworkException && attempt < 3) {
        delay(1000L * (attempt + 1))
        true  // retry
    } else {
        false  // give up
    }
}
```

---

### 6. Spring WebFlux (MEDIUM)

#### `webflux-controller` - Use Suspend in Controllers

**Impact**: Non-blocking endpoints, efficient resource use

```kotlin
// ✅ CORRECT - suspend function in controller
@RestController
@RequestMapping("/api/users")
class UserController(
    private val userService: UserService
) {
    @GetMapping("/{id}")
    suspend fun getUser(@PathVariable id: Long): User {
        return userService.findById(id)
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
    }

    @GetMapping
    fun getAllUsers(): Flow<User> {  // Flow for streaming
        return userService.findAll()
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    suspend fun createUser(@RequestBody user: User): User {
        return userService.save(user)
    }
}
```

#### `webflux-coRouter` - Use coRouter DSL

**Impact**: Coroutine-friendly functional routing

```kotlin
// ✅ CORRECT - coRouter for functional endpoints
@Configuration
class RouterConfig(
    private val userHandler: UserHandler
) {
    @Bean
    fun userRoutes() = coRouter {
        "/api/users".nest {
            GET("", userHandler::getAllUsers)
            GET("/{id}", userHandler::getUser)
            POST("", userHandler::createUser)
            PUT("/{id}", userHandler::updateUser)
            DELETE("/{id}", userHandler::deleteUser)
        }

        filter { request, next ->
            // logging filter
            val start = System.currentTimeMillis()
            val response = next(request)
            val duration = System.currentTimeMillis() - start
            logger.info("${request.method()} ${request.path()} - ${duration}ms")
            response
        }
    }
}

@Component
class UserHandler(
    private val userService: UserService
) {
    suspend fun getUser(request: ServerRequest): ServerResponse {
        val id = request.pathVariable("id").toLong()
        val user = userService.findById(id)
        return if (user != null) {
            ServerResponse.ok().bodyValueAndAwait(user)
        } else {
            ServerResponse.notFound().buildAndAwait()
        }
    }

    suspend fun getAllUsers(request: ServerRequest): ServerResponse {
        val users = userService.findAll()
        return ServerResponse.ok().bodyAndAwait(users)
    }
}
```

#### `webflux-webclient` - Use WebClient with awaitBody

**Impact**: Non-blocking HTTP client calls

```kotlin
// ✅ CORRECT - WebClient with coroutine extensions
@Service
class ExternalApiService(
    private val webClient: WebClient
) {
    suspend fun fetchUser(userId: String): ExternalUser {
        return webClient.get()
            .uri("/users/{id}", userId)
            .retrieve()
            .awaitBody()
    }

    suspend fun fetchUserOrNull(userId: String): ExternalUser? {
        return webClient.get()
            .uri("/users/{id}", userId)
            .retrieve()
            .awaitBodyOrNull()
    }

    suspend fun createUser(user: ExternalUser): ExternalUser {
        return webClient.post()
            .uri("/users")
            .bodyValue(user)
            .retrieve()
            .awaitBody()
    }

    // Stream response
    fun fetchUsers(): Flow<ExternalUser> {
        return webClient.get()
            .uri("/users")
            .retrieve()
            .bodyToFlow()
    }
}

// ✅ CORRECT - WebClient configuration
@Configuration
class WebClientConfig {
    @Bean
    fun webClient(): WebClient {
        return WebClient.builder()
            .baseUrl("https://api.example.com")
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .filter(ExchangeFilterFunction.ofRequestProcessor { request ->
                logger.debug("Request: ${request.method()} ${request.url()}")
                Mono.just(request)
            })
            .build()
    }
}
```

#### `webflux-context` - Propagate ReactorContext

**Impact**: Observability, tracing, MDC propagation

```kotlin
// ✅ CORRECT - context propagation in Spring Boot 4
// application.properties: spring.reactor.context-propagation=auto

// For earlier versions or custom context
@Configuration
class ContextPropagationConfig {
    @Bean
    fun contextPropagationHook(): Hooks.OnEachOperator {
        return { publisher ->
            if (publisher is Mono<*>) {
                publisher.contextWrite { ctx ->
                    // Copy MDC to reactor context
                    val mdc = MDC.getCopyOfContextMap() ?: emptyMap()
                    ctx.putAll(mdc.mapKeys { (k, _) -> k })
                }
            } else {
                publisher
            }
        }
    }
}

// ✅ CORRECT - propagate context in coroutines
suspend fun <T> withMdcContext(block: suspend () -> T): T {
    return withContext(MDCContext()) {
        block()
    }
}

@GetMapping("/traced")
suspend fun tracedEndpoint(): Response {
    return withMdcContext {
        MDC.put("requestId", UUID.randomUUID().toString())
        logger.info("Processing request")  // MDC available
        service.process()
    }
}
```

#### `webflux-response` - Return Proper Types

**Impact**: Efficient serialization, proper HTTP semantics

```kotlin
// ✅ CORRECT - return appropriate types
@RestController
class ApiController(
    private val itemService: ItemService
) {
    // Single item - suspend function
    @GetMapping("/items/{id}")
    suspend fun getItem(@PathVariable id: Long): ResponseEntity<Item> {
        val item = itemService.findById(id)
        return if (item != null) {
            ResponseEntity.ok(item)
        } else {
            ResponseEntity.notFound().build()
        }
    }

    // Multiple items - Flow for streaming
    @GetMapping("/items", produces = [MediaType.APPLICATION_NDJSON_VALUE])
    fun getItems(): Flow<Item> {
        return itemService.findAll()
    }

    // Server-Sent Events
    @GetMapping("/items/stream", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
    fun streamItems(): Flow<ServerSentEvent<Item>> {
        return itemService.findAll().map { item ->
            ServerSentEvent.builder(item)
                .id(item.id.toString())
                .event("item")
                .build()
        }
    }

    // No content response
    @DeleteMapping("/items/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    suspend fun deleteItem(@PathVariable id: Long) {
        itemService.delete(id)
    }
}
```

#### `webflux-filter` - Use CoWebFilter

**Impact**: Coroutine-aware request filtering

```kotlin
// ✅ CORRECT - CoWebFilter for coroutine-based filtering
@Component
class LoggingFilter : CoWebFilter() {

    override suspend fun filter(exchange: ServerWebExchange, chain: CoWebFilterChain) {
        val request = exchange.request
        val start = System.currentTimeMillis()
        val requestId = UUID.randomUUID().toString()

        // Add request ID to context
        exchange.attributes["requestId"] = requestId

        try {
            chain.filter(exchange)
        } finally {
            val duration = System.currentTimeMillis() - start
            logger.info(
                "[$requestId] ${request.method} ${request.path} - ${duration}ms"
            )
        }
    }
}

// ✅ CORRECT - authentication filter
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
class AuthenticationFilter(
    private val tokenService: TokenService
) : CoWebFilter() {

    override suspend fun filter(exchange: ServerWebExchange, chain: CoWebFilterChain) {
        val token = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)
            ?.removePrefix("Bearer ")

        if (token != null) {
            val user = tokenService.validateAndGetUser(token)
            if (user != null) {
                exchange.attributes["currentUser"] = user
            }
        }

        chain.filter(exchange)
    }
}
```

---

### 7. Security (MEDIUM)

#### `sec-reactive` - Use ReactiveSecurityContextHolder

**Impact**: Reactive-compatible security context access

```kotlin
// ❌ INCORRECT - blocking SecurityContextHolder
suspend fun getCurrentUser(): User {
    val auth = SecurityContextHolder.getContext().authentication  // May be empty!
    return auth.principal as User
}

// ✅ CORRECT - ReactiveSecurityContextHolder with coroutines
suspend fun getCurrentUser(): User {
    return ReactiveSecurityContextHolder.getContext()
        .awaitSingleOrNull()
        ?.authentication
        ?.principal as? User
        ?: throw UnauthorizedException()
}

// ✅ CORRECT - as extension function
suspend fun <T : Principal> ReactiveSecurityContextHolder.currentUserOrNull(): T? {
    return getContext()
        .awaitSingleOrNull()
        ?.authentication
        ?.principal as? T
}

// Usage
@GetMapping("/me")
suspend fun getProfile(): UserProfile {
    val user = ReactiveSecurityContextHolder.currentUserOrNull<User>()
        ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
    return userService.getProfile(user.id)
}
```

#### `sec-method` - Use @PreAuthorize with Suspend

**Impact**: Method-level security on suspend functions

```kotlin
// ✅ CORRECT - enable reactive method security
@Configuration
@EnableReactiveMethodSecurity
class SecurityConfig

// ✅ CORRECT - @PreAuthorize on suspend functions
@Service
class AdminService {

    @PreAuthorize("hasRole('ADMIN')")
    suspend fun deleteUser(userId: Long) {
        userRepository.deleteById(userId)
    }

    @PreAuthorize("hasRole('ADMIN') or #userId == principal.id")
    suspend fun updateUser(userId: Long, update: UserUpdate): User {
        return userRepository.update(userId, update)
    }

    @PreAuthorize("@permissionService.canAccess(principal, #resourceId)")
    suspend fun accessResource(resourceId: Long): Resource {
        return resourceRepository.findById(resourceId)
    }
}
```

#### `sec-context-propagation` - Propagate SecurityContext

**Impact**: Security context available across async boundaries

```kotlin
// ✅ CORRECT - propagate security context to coroutines
@Configuration
class SecurityContextConfig {

    @Bean
    fun securityContextCoroutineContext(): CoroutineContext {
        return ReactorContext(
            Context.of(SecurityContext::class.java,
                ReactiveSecurityContextHolder.getContext())
        )
    }
}

// ✅ CORRECT - custom context element for security
class SecurityContextElement(
    val securityContext: SecurityContext
) : AbstractCoroutineContextElement(Key) {
    companion object Key : CoroutineContext.Key<SecurityContextElement>
}

suspend fun withSecurityContext(block: suspend () -> Unit) {
    val securityContext = ReactiveSecurityContextHolder.getContext()
        .awaitSingleOrNull()

    if (securityContext != null) {
        withContext(SecurityContextElement(securityContext)) {
            block()
        }
    } else {
        block()
    }
}
```

#### `sec-jwt` - Implement JWT Properly

**Impact**: Stateless authentication, token validation

```kotlin
// ✅ CORRECT - JWT configuration for WebFlux
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http
            .csrf { it.disable() }
            .httpBasic { it.disable() }
            .formLogin { it.disable() }
            .authorizeExchange { auth ->
                auth.pathMatchers("/api/auth/**").permitAll()
                auth.pathMatchers("/api/admin/**").hasRole("ADMIN")
                auth.anyExchange().authenticated()
            }
            .oauth2ResourceServer { oauth2 ->
                oauth2.jwt { jwt ->
                    jwt.jwtAuthenticationConverter(jwtAuthConverter())
                }
            }
            .build()
    }

    @Bean
    fun jwtAuthConverter(): ReactiveJwtAuthenticationConverter {
        val converter = ReactiveJwtAuthenticationConverter()
        converter.setJwtGrantedAuthoritiesConverter { jwt ->
            val roles = jwt.getClaimAsStringList("roles") ?: emptyList()
            Flux.fromIterable(roles.map { SimpleGrantedAuthority("ROLE_$it") })
        }
        return converter
    }
}

// ✅ CORRECT - JWT token service
@Service
class JwtTokenService(
    @Value("\${jwt.secret}") private val secret: String,
    @Value("\${jwt.expiration}") private val expiration: Long
) {
    private val key = Keys.hmacShaKeyFor(secret.toByteArray())

    fun generateToken(user: User): String {
        val now = Date()
        val expiryDate = Date(now.time + expiration)

        return Jwts.builder()
            .setSubject(user.id.toString())
            .claim("email", user.email)
            .claim("roles", user.roles)
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(key, SignatureAlgorithm.HS256)
            .compact()
    }

    suspend fun validateToken(token: String): User? {
        return try {
            val claims = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .body

            val userId = claims.subject.toLong()
            userRepository.findById(userId)
        } catch (e: Exception) {
            null
        }
    }
}
```

#### `sec-cors` - Configure CORS Correctly

**Impact**: Cross-origin security for SPAs

```kotlin
// ✅ CORRECT - CORS configuration for WebFlux
@Configuration
class CorsConfig {

    @Bean
    fun corsConfigurationSource(): CorsConfigurationSource {
        val configuration = CorsConfiguration().apply {
            allowedOrigins = listOf(
                "https://myapp.com",
                "https://staging.myapp.com"
            )
            allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
            allowedHeaders = listOf(
                HttpHeaders.AUTHORIZATION,
                HttpHeaders.CONTENT_TYPE,
                "X-Request-ID"
            )
            exposedHeaders = listOf("X-Total-Count", "X-Page-Count")
            allowCredentials = true
            maxAge = 3600
        }

        return UrlBasedCorsConfigurationSource().apply {
            registerCorsConfiguration("/api/**", configuration)
        }
    }
}

// In SecurityConfig
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http
        .cors { it.configurationSource(corsConfigurationSource()) }
        // ... rest of config
        .build()
}
```

#### `sec-csrf` - Handle CSRF in WebFlux

**Impact**: Request forgery protection

```kotlin
// ✅ CORRECT - CSRF for stateless APIs (typically disabled)
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http
            // For stateless JWT APIs, CSRF is typically disabled
            .csrf { it.disable() }
            // ... rest of config
            .build()
    }
}

// ✅ CORRECT - CSRF for stateful apps with cookies
@Configuration
@EnableWebFluxSecurity
class StatefulSecurityConfig {

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http
            .csrf { csrf ->
                csrf.csrfTokenRepository(
                    CookieServerCsrfTokenRepository.withHttpOnlyFalse()
                )
            }
            // ... rest of config
            .build()
    }
}
```

---

### 8. Testing (MEDIUM)

#### `test-runTest` - Use runTest for Coroutines

**Impact**: Proper test execution, virtual time control

```kotlin
// ✅ CORRECT - use runTest for coroutine tests
class UserServiceTest {

    private val userRepository = mockk<UserRepository>()
    private val userService = UserService(userRepository)

    @Test
    fun `should return user by id`() = runTest {
        // Given
        val user = User(1L, "test@example.com")
        coEvery { userRepository.findById(1L) } returns user

        // When
        val result = userService.getUser(1L)

        // Then
        assertEquals(user, result)
        coVerify { userRepository.findById(1L) }
    }

    @Test
    fun `should handle delay correctly`() = runTest {
        // Given
        coEvery { userRepository.findById(any()) } coAnswers {
            delay(1000)  // Virtual time - completes instantly
            User(1L, "test@example.com")
        }

        // When
        val result = userService.getUser(1L)

        // Then
        assertNotNull(result)
        // Test completes instantly despite 1000ms delay
    }
}
```

#### `test-mockk` - Use MockK coEvery/coVerify

**Impact**: Suspend function mocking, clean test syntax

```kotlin
// ✅ CORRECT - MockK for coroutine mocking
class OrderServiceTest {

    private val orderRepository = mockk<OrderRepository>()
    private val paymentService = mockk<PaymentService>()
    private val orderService = OrderService(orderRepository, paymentService)

    @Test
    fun `should create order with payment`() = runTest {
        // Given
        val order = Order(productId = 1L, quantity = 2)
        val savedOrder = order.copy(id = 1L)

        coEvery { orderRepository.save(any()) } returns savedOrder
        coEvery { paymentService.processPayment(any()) } returns PaymentResult.SUCCESS

        // When
        val result = orderService.createOrder(order)

        // Then
        assertEquals(1L, result.id)

        coVerify(exactly = 1) { orderRepository.save(order) }
        coVerify(exactly = 1) { paymentService.processPayment(savedOrder) }
    }

    @Test
    fun `should handle payment failure`() = runTest {
        // Given
        coEvery { orderRepository.save(any()) } returns Order(id = 1L)
        coEvery { paymentService.processPayment(any()) } throws PaymentException("Failed")

        // When/Then
        assertThrows<PaymentException> {
            orderService.createOrder(Order())
        }
    }
}
```

#### `test-turbine` - Use Turbine for Flow Testing

**Impact**: Clean Flow assertions, timeout handling

```kotlin
// build.gradle.kts
// testImplementation("app.cash.turbine:turbine:1.0.0")

// ✅ CORRECT - Turbine for Flow testing
class EventServiceTest {

    private val eventService = EventService()

    @Test
    fun `should emit events in order`() = runTest {
        eventService.events.test {
            // Trigger events
            eventService.emit(Event("first"))
            eventService.emit(Event("second"))

            // Assert emissions
            assertEquals(Event("first"), awaitItem())
            assertEquals(Event("second"), awaitItem())

            // Cancel and ensure no more events
            cancelAndIgnoreRemainingEvents()
        }
    }

    @Test
    fun `should complete flow`() = runTest {
        val flow = flowOf(1, 2, 3)

        flow.test {
            assertEquals(1, awaitItem())
            assertEquals(2, awaitItem())
            assertEquals(3, awaitItem())
            awaitComplete()
        }
    }

    @Test
    fun `should handle flow errors`() = runTest {
        val errorFlow = flow {
            emit(1)
            throw RuntimeException("Error")
        }

        errorFlow.test {
            assertEquals(1, awaitItem())
            val error = awaitError()
            assertEquals("Error", error.message)
        }
    }
}
```

#### `test-dispatcher` - Use TestDispatcher

**Impact**: Deterministic tests, controlled execution

```kotlin
// ✅ CORRECT - TestDispatcher for controlled execution
class TimerServiceTest {

    @Test
    fun `should emit at intervals`() = runTest {
        val testDispatcher = StandardTestDispatcher(testScheduler)
        val timerService = TimerService(testDispatcher)

        val emissions = mutableListOf<Long>()
        val job = launch {
            timerService.ticker(1000).take(3).collect { emissions.add(it) }
        }

        // Advance time manually
        advanceTimeBy(3000)
        runCurrent()

        assertEquals(3, emissions.size)
        job.cancel()
    }

    @Test
    fun `should use UnconfinedTestDispatcher for immediate execution`() = runTest {
        val testDispatcher = UnconfinedTestDispatcher(testScheduler)

        var executed = false
        launch(testDispatcher) {
            executed = true
        }

        assertTrue(executed)  // Executed immediately
    }
}

// ✅ CORRECT - inject dispatcher for testability
class DataRefresher(
    private val dispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    suspend fun refresh() = withContext(dispatcher) {
        // ...
    }
}

// In test
@Test
fun `should refresh data`() = runTest {
    val refresher = DataRefresher(StandardTestDispatcher(testScheduler))
    refresher.refresh()
    advanceUntilIdle()
    // assertions
}
```

#### `test-webclient` - Test with WebTestClient

**Impact**: Integration testing for WebFlux endpoints

```kotlin
// ✅ CORRECT - WebTestClient for integration tests
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerIntegrationTest {

    @Autowired
    lateinit var webTestClient: WebTestClient

    @Test
    fun `should get user by id`() {
        webTestClient.get()
            .uri("/api/users/1")
            .exchange()
            .expectStatus().isOk
            .expectBody<User>()
            .consumeWith { result ->
                val user = result.responseBody!!
                assertEquals(1L, user.id)
            }
    }

    @Test
    fun `should create user`() {
        val newUser = UserCreate("test@example.com", "Test User")

        webTestClient.post()
            .uri("/api/users")
            .bodyValue(newUser)
            .exchange()
            .expectStatus().isCreated
            .expectBody<User>()
            .consumeWith { result ->
                val user = result.responseBody!!
                assertNotNull(user.id)
                assertEquals("test@example.com", user.email)
            }
    }

    @Test
    fun `should return 404 for missing user`() {
        webTestClient.get()
            .uri("/api/users/999")
            .exchange()
            .expectStatus().isNotFound
    }

    // ✅ CORRECT - test Flow endpoints
    @Test
    fun `should stream users`() {
        webTestClient.get()
            .uri("/api/users/stream")
            .accept(MediaType.APPLICATION_NDJSON)
            .exchange()
            .expectStatus().isOk
            .returnResult<User>()
            .responseBody
            .test()
            .expectNextCount(10)
            .verifyComplete()
    }
}
```

#### `test-container` - Use Testcontainers

**Impact**: Real database testing, production-like tests

```kotlin
// build.gradle.kts
// testImplementation("org.testcontainers:postgresql:1.19.0")
// testImplementation("org.testcontainers:r2dbc:1.19.0")

// ✅ CORRECT - Testcontainers with R2DBC
@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {

    companion object {
        @Container
        val postgres = PostgreSQLContainer("postgres:15-alpine")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test")

        @JvmStatic
        @DynamicPropertySource
        fun properties(registry: DynamicPropertyRegistry) {
            registry.add("spring.r2dbc.url") {
                "r2dbc:postgresql://${postgres.host}:${postgres.getMappedPort(5432)}/${postgres.databaseName}"
            }
            registry.add("spring.r2dbc.username") { postgres.username }
            registry.add("spring.r2dbc.password") { postgres.password }
            registry.add("spring.flyway.url") { postgres.jdbcUrl }
            registry.add("spring.flyway.user") { postgres.username }
            registry.add("spring.flyway.password") { postgres.password }
        }
    }

    @Autowired
    lateinit var userRepository: UserRepository

    @Test
    fun `should save and retrieve user`() = runTest {
        // Given
        val user = User(email = "test@example.com", name = "Test User")

        // When
        val saved = userRepository.save(user)
        val retrieved = userRepository.findById(saved.id!!)

        // Then
        assertNotNull(retrieved)
        assertEquals("test@example.com", retrieved?.email)
    }

    @Test
    fun `should find users by email`() = runTest {
        // Given
        userRepository.save(User(email = "find@example.com", name = "Find Me"))

        // When
        val found = userRepository.findByEmail("find@example.com")

        // Then
        assertNotNull(found)
        assertEquals("Find Me", found?.name)
    }
}
```

---

## Project Structure

```
src/
├── main/kotlin/com/example/
│   ├── Application.kt              # Main application
│   ├── config/
│   │   ├── CoroutineConfig.kt     # Coroutine scope beans
│   │   ├── R2dbcConfig.kt         # Database configuration
│   │   ├── SecurityConfig.kt      # Security configuration
│   │   └── WebClientConfig.kt     # HTTP client configuration
│   ├── controller/
│   │   └── UserController.kt      # REST controllers
│   ├── domain/
│   │   └── User.kt                # Domain entities
│   ├── repository/
│   │   └── UserRepository.kt      # R2DBC repositories
│   ├── service/
│   │   └── UserService.kt         # Business logic
│   └── exception/
│       └── Exceptions.kt          # Custom exceptions
├── main/resources/
│   ├── application.yml            # Configuration
│   └── db/migration/              # Flyway migrations
└── test/kotlin/com/example/
    ├── controller/
    │   └── UserControllerTest.kt
    ├── service/
    │   └── UserServiceTest.kt
    └── repository/
        └── UserRepositoryTest.kt
```

## Integration Workflow

When implementing Kotlin Coroutine + Spring features:

1. **Before implementation**: Review relevant rule categories
2. **During development**: Apply patterns from quick reference
3. **Code review**: Verify against rule checklist
4. **Performance issues**: Consult detailed rules for solutions

```
coroutine-* rules → Proper suspend functions, no blocking
    ↓
scope-* rules → Structured concurrency, no GlobalScope
    ↓
flow-* rules → Correct Flow patterns and operators
    ↓
db-* rules → R2DBC, transactions, N+1 prevention
    ↓
err-* rules → SupervisorJob, proper exception handling
    ↓
webflux-* rules → Non-blocking endpoints, context propagation
    ↓
sec-* rules → Reactive security, JWT, context
    ↓
test-* rules → runTest, MockK, Turbine
```

## Compliance Checklist

Before submitting Kotlin Coroutine + Spring code:

- [ ] No blocking calls in suspend functions (`coroutine-blocking`)
- [ ] Correct dispatcher for CPU vs IO (`coroutine-dispatcher`)
- [ ] No GlobalScope usage (`scope-no-global`)
- [ ] Structured concurrency with coroutineScope/supervisorScope (`scope-structured`)
- [ ] StateFlow/SharedFlow used appropriately (`flow-stateflow`, `flow-sharedflow`)
- [ ] R2DBC with CoroutineCrudRepository (`db-r2dbc`, `db-repository`)
- [ ] Transactions handled correctly (`db-transaction`)
- [ ] CancellationException rethrown (`err-cancellation`)
- [ ] SupervisorJob with exception handler (`err-supervisor`)
- [ ] Security context propagated (`sec-context-propagation`)
- [ ] Tests use runTest with MockK (`test-runTest`, `test-mockk`)


---
Source attribution: David-Li0406
Original project: https://github.com/David-Li0406/meta-skill-evloving/blob/ca3a335628981df10c36e00cb9850df2c247ab9a/skill-flow/data/skills-refined-36k/skillsmp/best-practices-8/SKILL.md
License: Apache-2.0
License information: https://www.apache.org/licenses/LICENSE-2.0
Original upstream instructions; downloaded from Mega AI Hub.

## Upstream license notice

Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.



# Mega AI Hub resource metadata

Creator / publisher: David-Li0406
Original project: https://github.com/David-Li0406/meta-skill-evloving/blob/ca3a335628981df10c36e00cb9850df2c247ab9a/skill-flow/data/skills-refined-36k/skillsmp/best-practices-8/SKILL.md
Attribution basis: Source repository record

Fields of work: Development & Engineering, Product Management
Roles: Backend Developer, DevOps / SRE, Product Manager
Specialties: Backend
Tools: Spring Boot
Country applicability: Country not specified
Classification basis: Source catalog labels
Instruction SHA-256: 2ebaa83b6521a3206e76deaecd90633b152f1bd8cf0f363232b9dd8f4bf43afe
