home / skills / physics91 / claude-vibe / kotlin-spring-reviewer

kotlin-spring-reviewer skill

/skills/kotlin-spring-reviewer

This skill reviews Spring Kotlin projects for idioms, coroutine integration, WebFlux patterns, and data class practices to improve reliability.

npx playbooks add skill physics91/claude-vibe --skill kotlin-spring-reviewer

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.9 KB
---
name: kotlin-spring-reviewer
description: |
  WHEN: Spring Boot + Kotlin, Ktor backend review, coroutine-based server, WebFlux/R2DBC pattern checks
  WHAT: Spring Kotlin idioms + Coroutines integration + WebFlux patterns + Data class usage + Test strategies
  WHEN NOT: Android → kotlin-android-reviewer, KMP shared code → kotlin-multiplatform-reviewer
---

# Kotlin Spring Reviewer Skill

## Purpose
Reviews Spring Boot + Kotlin and Ktor backend projects for Kotlin idioms, Coroutines integration, WebFlux, and data class best practices.

## When to Use
- Spring Boot + Kotlin project code review
- Ktor server project review
- "WebFlux", "R2DBC", "Coroutines server" mentions
- Projects with `spring-boot` or `ktor` in `build.gradle.kts`

## Project Detection
- `org.springframework.boot` plugin in `build.gradle.kts`
- `io.ktor` dependency in `build.gradle.kts`
- `application.yml` or `application.properties`
- `Application.kt` main class

## Workflow

### Step 1: Analyze Project
```
**Framework**: Spring Boot 3.2.x
**Kotlin**: 1.9.x
**Build Tool**: Gradle (Kotlin DSL)
**Dependencies**:
  - Spring WebFlux (reactive)
  - Spring Data R2DBC
  - Kotlinx Coroutines
```

### Step 2: Select Review Areas
**AskUserQuestion:**
```
"Which areas to review?"
Options:
- Full Kotlin Spring pattern check (recommended)
- Kotlin idiom usage
- Coroutines/WebFlux integration
- Data class/DTO design
- Test strategies
multiSelect: true
```

## Detection Rules

### Kotlin Idioms
| Check | Recommendation | Severity |
|-------|----------------|----------|
| Java-style getter/setter | Use Kotlin property | LOW |
| if-based null check | Use ?.let, ?:, avoid !! | MEDIUM |
| if-else chain | Use when expression | LOW |
| Missing extension functions | Utility → extension function | LOW |
| Missing scope functions | Use apply, let, run, also | LOW |

```kotlin
// BAD: Java style
class User {
    private var name: String? = null
    fun getName(): String? = name
    fun setName(name: String?) { this.name = name }
}

// GOOD: Kotlin property
class User {
    var name: String? = null
}

// BAD: Java-style null check
fun process(user: User?) {
    if (user != null) {
        if (user.name != null) {
            println(user.name)
        }
    }
}

// GOOD: Kotlin null-safe operators
fun process(user: User?) {
    user?.name?.let { println(it) }
}

// BAD: if-else chain
fun getStatus(code: Int): String {
    if (code == 200) return "OK"
    else if (code == 404) return "Not Found"
    else return "Unknown"
}

// GOOD: when expression
fun getStatus(code: Int): String = when (code) {
    200 -> "OK"
    404 -> "Not Found"
    else -> "Unknown"
}
```

### Spring + Kotlin Patterns
| Check | Recommendation | Severity |
|-------|----------------|----------|
| @Autowired field injection | Constructor injection | HIGH |
| lateinit var abuse | Constructor injection or lazy | MEDIUM |
| Missing open class | Use all-open plugin | HIGH |
| data class @Entity | Use regular class | HIGH |

```kotlin
// BAD: Field injection
@Service
class UserService {
    @Autowired
    private lateinit var userRepository: UserRepository
}

// GOOD: Constructor injection (Kotlin default)
@Service
class UserService(
    private val userRepository: UserRepository
)

// BAD: data class as JPA Entity
@Entity
data class User(
    @Id val id: Long,
    val name: String
)  // equals/hashCode issues

// GOOD: Regular class with explicit equals/hashCode
@Entity
class User(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,
    var name: String
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is User) return false
        return id != null && id == other.id
    }
    override fun hashCode(): Int = javaClass.hashCode()
}
```

**Gradle Plugin Check:**
```kotlin
// build.gradle.kts
plugins {
    kotlin("plugin.spring")  // all-open for Spring
    kotlin("plugin.jpa")     // no-arg for JPA entities
}
```

### Coroutines Integration
| Check | Recommendation | Severity |
|-------|----------------|----------|
| runBlocking in controller | Use suspend function | CRITICAL |
| GlobalScope in server | Use structured concurrency | CRITICAL |
| Missing Dispatcher | Specify IO/Default | MEDIUM |
| Missing exception handling | Use CoroutineExceptionHandler | HIGH |

```kotlin
// BAD: runBlocking in controller
@GetMapping("/users")
fun getUsers(): List<User> = runBlocking {
    userService.getUsers()
}

// GOOD: suspend function (WebFlux/Coroutines)
@GetMapping("/users")
suspend fun getUsers(): List<User> =
    userService.getUsers()

// BAD: GlobalScope in service
@Service
class UserService {
    fun processAsync() {
        GlobalScope.launch {
            // Dangerous: Not cancelled on app shutdown
        }
    }
}

// GOOD: Structured concurrency
@Service
class UserService(
    private val applicationScope: CoroutineScope
) {
    fun processAsync() = applicationScope.launch {
        // Properly cancelled on app shutdown
    }
}
```

### WebFlux + Coroutines
| Check | Recommendation | Severity |
|-------|----------------|----------|
| Direct Mono/Flux usage | Convert to suspend/Flow | MEDIUM |
| awaitSingle abuse | Use coRouter DSL | LOW |
| Blocking call | Use Dispatchers.IO | CRITICAL |

```kotlin
// OK: Direct Mono/Flux
@GetMapping("/user/{id}")
fun getUser(@PathVariable id: Long): Mono<User> =
    userRepository.findById(id)

// BETTER: Kotlin Coroutines
@GetMapping("/user/{id}")
suspend fun getUser(@PathVariable id: Long): User? =
    userRepository.findById(id).awaitSingleOrNull()

// BEST: coRouter DSL (functional endpoints)
@Configuration
class RouterConfig {
    @Bean
    fun routes(handler: UserHandler) = coRouter {
        "/api/users".nest {
            GET("", handler::getAll)
            GET("/{id}", handler::getById)
            POST("", handler::create)
        }
    }
}

class UserHandler(private val service: UserService) {
    suspend fun getAll(request: ServerRequest): ServerResponse =
        ServerResponse.ok().bodyAndAwait(service.getAll())
}
```

### Ktor Patterns
| Check | Recommendation | Severity |
|-------|----------------|----------|
| Excessive routing nesting | Split into modules | MEDIUM |
| No DI | Use Koin/Kodein | MEDIUM |
| Missing error handling | Use StatusPages plugin | HIGH |
| Missing serialization | Use ContentNegotiation | HIGH |

```kotlin
// BAD: All routes in one file
fun Application.module() {
    routing {
        get("/users") { /* ... */ }
        get("/users/{id}") { /* ... */ }
        get("/products") { /* ... */ }
        // ... 100 more
    }
}

// GOOD: Split into modules
fun Application.module() {
    configureRouting()
    configureSerialization()
    configureDI()
}

fun Application.configureRouting() {
    routing {
        userRoutes()
        productRoutes()
    }
}

fun Route.userRoutes() {
    route("/users") {
        get { /* ... */ }
        get("/{id}") { /* ... */ }
        post { /* ... */ }
    }
}
```

### Data Class Design
| Check | Recommendation | Severity |
|-------|----------------|----------|
| var in DTO | Use val (immutable) | MEDIUM |
| Excessive nullable | Use defaults or required | LOW |
| Missing validation | Use @field:Valid, init {} | MEDIUM |

```kotlin
// BAD: Mutable DTO
data class CreateUserRequest(
    var name: String?,
    var email: String?
)

// GOOD: Immutable + validation
data class CreateUserRequest(
    @field:NotBlank
    val name: String,

    @field:Email
    val email: String
) {
    init {
        require(name.length <= 100) { "Name too long" }
    }
}
```

## Response Template
```
## Kotlin Spring Code Review Results

**Project**: [name]
**Spring Boot**: 3.2.x | **Kotlin**: 1.9.x
**Stack**: WebFlux + R2DBC + Coroutines

### Kotlin Idioms
| Status | File | Issue |
|--------|------|-------|
| LOW | UserService.kt | Java-style null check → ?.let recommended |

### Spring Patterns
| Status | File | Issue |
|--------|------|-------|
| HIGH | ProductService.kt | @Autowired field injection → constructor injection |
| HIGH | User.kt | data class @Entity → regular class |

### Coroutines
| Status | File | Issue |
|--------|------|-------|
| CRITICAL | ReportService.kt | runBlocking in controller |
| HIGH | BatchJob.kt | GlobalScope usage |

### Recommended Actions
1. [ ] Verify kotlin-spring, kotlin-jpa plugins
2. [ ] runBlocking → suspend function conversion
3. [ ] GlobalScope → applicationScope injection
4. [ ] data class Entity → regular class change
```

## Best Practices
1. **Constructor Injection**: Use default constructor instead of @Autowired
2. **Immutability**: val, data class (except Entity)
3. **Coroutines**: suspend functions, structured concurrency
4. **Kotlin DSL**: coRouter, bean { }
5. **Testing**: MockK, Kotest, @SpringBootTest

## Integration
- `code-reviewer` skill: General code quality
- `kotlin-multiplatform-reviewer` skill: KMP server sharing
- `security-scanner` skill: API security checks

## Notes
- Based on Spring Boot 3.x + Kotlin 1.9+
- WebFlux/R2DBC reactive stack support
- Ktor 2.x support

Overview

This skill reviews Spring Boot + Kotlin and Ktor backend projects to surface Kotlin idiom issues, coroutine integration problems, WebFlux/R2DBC anti-patterns, and data class design pitfalls. It highlights high-severity problems (e.g., runBlocking, GlobalScope, field injection, data class entities) and gives concrete remediation steps. The goal is practical, actionable feedback to improve correctness, performance, and maintainability of coroutine-based servers.

How this skill works

The reviewer scans build files, main application entries, and Kotlin source to detect Spring Boot or Ktor projects and the reactive stack (WebFlux, R2DBC). It runs rule checks for Kotlin idioms, Spring-specific patterns (injection, all-open/jpa plugins), coroutine usage, WebFlux-to-coroutines conversions, Ktor routing/DI, and DTO/entity design. Results are categorized by severity and include suggested fixes like converting runBlocking to suspend, replacing GlobalScope with structured application scope, and converting field injection to constructor injection.

When to use it

  • Spring Boot + Kotlin backend projects (WebFlux, R2DBC, coroutines)
  • Ktor server projects that use coroutines or routing modules
  • Code bases mentioning spring-boot or ktor in build.gradle.kts or having Application.kt
  • Reviews focused on coroutine safety, reactive integration, or data class design
  • Before release or during PR review to catch high-risk concurrency and Spring misconfiguration

Best practices

  • Prefer constructor injection; avoid @Autowired field injection and lateinit abuse
  • Use suspend functions and structured concurrency instead of runBlocking or GlobalScope
  • Keep entities as regular classes (not data classes) and use all-open/no-arg plugins for Spring/JPA
  • Favor immutable DTOs (val) with validation annotations and minimal nullable fields
  • Convert Mono/Flux endpoints to suspend/Flow or coRouter functional endpoints when using coroutines
  • Use Dispatchers.IO for blocking operations and CoroutineExceptionHandler for centralized error handling

Example use cases

  • Audit a Spring WebFlux + R2DBC service that mixes Mono/Flux and suspend functions
  • Review a Ktor 2.x application for routing modularization, DI usage, and error handling
  • Identify coroutine anti-patterns introduced in controllers or services (runBlocking, GlobalScope)
  • Check entity and DTO definitions to avoid data class JPA entities and mutable request objects
  • Validate Gradle Kotlin DSL plugins and project setup for kotlin-spring and kotlin-jpa

FAQ

Does the reviewer support both WebFlux and Ktor?

Yes. It checks Spring WebFlux + R2DBC reactive stacks and Ktor 2.x server patterns, applying relevant coroutine and routing rules for each framework.

Will it change code automatically?

No. The skill produces a prioritized checklist and concrete recommendations; automated fixes are left to the developer or separate tooling.