kotlin-spring-reviewer

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

$ Installieren

git clone https://github.com/majiayu000/claude-skill-registry /tmp/claude-skill-registry && cp -r /tmp/claude-skill-registry/skills/testing/kotlin-spring-reviewer ~/.claude/skills/claude-skill-registry

// tip: Run this command in your terminal to install the skill


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

CheckRecommendationSeverity
Java-style getter/setterUse Kotlin propertyLOW
if-based null checkUse ?.let, ?:, avoid !!MEDIUM
if-else chainUse when expressionLOW
Missing extension functionsUtility → extension functionLOW
Missing scope functionsUse apply, let, run, alsoLOW
// 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

CheckRecommendationSeverity
@Autowired field injectionConstructor injectionHIGH
lateinit var abuseConstructor injection or lazyMEDIUM
Missing open classUse all-open pluginHIGH
data class @EntityUse regular classHIGH
// 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:

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

Coroutines Integration

CheckRecommendationSeverity
runBlocking in controllerUse suspend functionCRITICAL
GlobalScope in serverUse structured concurrencyCRITICAL
Missing DispatcherSpecify IO/DefaultMEDIUM
Missing exception handlingUse CoroutineExceptionHandlerHIGH
// 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

CheckRecommendationSeverity
Direct Mono/Flux usageConvert to suspend/FlowMEDIUM
awaitSingle abuseUse coRouter DSLLOW
Blocking callUse Dispatchers.IOCRITICAL
// 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

CheckRecommendationSeverity
Excessive routing nestingSplit into modulesMEDIUM
No DIUse Koin/KodeinMEDIUM
Missing error handlingUse StatusPages pluginHIGH
Missing serializationUse ContentNegotiationHIGH
// 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

CheckRecommendationSeverity
var in DTOUse val (immutable)MEDIUM
Excessive nullableUse defaults or requiredLOW
Missing validationUse @field:Valid, init {}MEDIUM
// 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