Agent skill
spring-boot-kotlin
Spring Boot with Kotlin framework guardrails, patterns, and best practices. Use when working with Spring Boot Kotlin projects, or when the user mentions Spring Boot with Kotlin. Provides coroutine patterns, WebFlux, JPA, security, and idiomatic Kotlin-Spring guidelines.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/spring-boot-kotlin
Metadata
Additional technical details for this skill
- author
- samuel
- version
- 1.0
- category
- framework
- language
- kotlin
- extensions
- .kt
SKILL.md
Spring Boot (Kotlin) Framework Guide
Applies to: Spring Boot 3.x, Kotlin 1.9+, REST APIs, Microservices Use with:
.claude/skills/kotlin-guide/SKILL.mdfor base Kotlin patterns
When to Use
- Enterprise Applications: Proven enterprise-grade framework with vast ecosystem
- Microservices: Excellent with Spring Cloud for distributed systems
- Existing Spring Ecosystem: Leverage Spring libraries, security, data, cloud
- Complex Business Logic: Rich feature set for complex domains
- Team Familiarity: Teams with Java/Spring background transitioning to Kotlin
When NOT to Use
- Lightweight Services: Consider Ktor for simpler, minimal-dependency services
- Mobile Backend: Ktor is more lightweight for mobile-only backends
- Minimal Footprint: Spring Boot has a larger memory and startup footprint
Project Structure
myproject/
├── build.gradle.kts
├── settings.gradle.kts
├── src/
│ ├── main/
│ │ ├── kotlin/com/example/myproject/
│ │ │ ├── MyProjectApplication.kt
│ │ │ ├── config/
│ │ │ │ ├── SecurityConfig.kt
│ │ │ │ ├── WebConfig.kt
│ │ │ │ └── JwtProperties.kt
│ │ │ ├── controller/
│ │ │ │ └── UserController.kt
│ │ │ ├── service/
│ │ │ │ └── UserService.kt
│ │ │ ├── repository/
│ │ │ │ └── UserRepository.kt
│ │ │ ├── model/
│ │ │ │ ├── entity/User.kt
│ │ │ │ └── dto/UserDto.kt
│ │ │ ├── exception/
│ │ │ │ ├── GlobalExceptionHandler.kt
│ │ │ │ └── Exceptions.kt
│ │ │ └── security/
│ │ │ ├── JwtService.kt
│ │ │ ├── JwtAuthenticationFilter.kt
│ │ │ └── CurrentUser.kt
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── application-dev.yml
│ │ └── db/migration/
│ └── test/kotlin/com/example/myproject/
│ ├── controller/
│ ├── service/
│ └── integration/
└── README.md
Guardrails
Spring-Kotlin Specific
- Use constructor injection (Kotlin default, no
@Autowiredneeded) - Never use
lateinit varfor dependencies -- constructor injection is idiomatic - Use
@Transactional(readOnly = true)for all read-only operations - Use
@Validon all request body parameters for automatic validation - Use Spring profiles (
application-{profile}.yml) for environment config - Never hardcode secrets in config files -- use
${ENV_VAR:default}syntax - Use
@ConfigurationPropertiesdata classes for type-safe configuration - Prefer Kotlin data classes for DTOs with
@field:annotation target for validation - Use
openmodifier orplugin.spring(auto-opens@Componentclasses)
Entity Design
- Use
data classfor JPA entities withplugin.jpa(auto-generates no-arg constructors) - Prefer
valfor entity properties, usecopy()for updates - Use UUID or auto-generated IDs; nullable
val id: UUID? = nullfor new entities - Use
@CreationTimestampand@UpdateTimestampfor audit fields - Keep entities in
model/entity/, DTOs inmodel/dto/
Service Layer
- Mark all service classes with
@Service - Use
@Transactionalfor write operations,@Transactional(readOnly = true)for reads - Throw domain-specific exceptions (
NotFoundException,ConflictException) - Return DTOs from services, never expose entities to controllers
- Use Kotlin null safety: repository methods return
T?for nullable finds
Controller Layer
- Use
@RestControllerwith@RequestMappingfor base paths - Return
ResponseEntity<T>with explicit status codes - Use
@Valid @RequestBodyfor all incoming request bodies - Use
@PreAuthorizefor method-level security - Use
@PageableDefaultfor pagination parameters
Key Patterns
Application Entry Point
@SpringBootApplication
class MyProjectApplication
fun main(args: Array<String>) {
runApplication<MyProjectApplication>(*args)
}
Type-Safe Configuration
@ConfigurationProperties(prefix = "jwt")
data class JwtProperties(
val secret: String,
val expiration: Long,
)
# application.yml
jwt:
secret: ${JWT_SECRET:your-256-bit-secret-key-here}
expiration: 86400000
Entity with Data Class
@Entity
@Table(name = "users")
data class User(
@Id
@GeneratedValue(strategy = GenerationType.UUID)
val id: UUID? = null,
@Column(unique = true, nullable = false)
val email: String,
@Column(nullable = false)
val passwordHash: String,
@Column(nullable = false)
val name: String,
@Column(nullable = false)
val role: String = "USER",
@CreationTimestamp
@Column(updatable = false)
val createdAt: Instant? = null,
@UpdateTimestamp
val updatedAt: Instant? = null,
)
DTO with Validation
data class CreateUserRequest(
@field:NotBlank(message = "Email is required")
@field:Email(message = "Invalid email format")
val email: String,
@field:NotBlank(message = "Password is required")
@field:Size(min = 8, message = "Password must be at least 8 characters")
val password: String,
@field:NotBlank(message = "Name is required")
@field:Size(min = 1, max = 100)
val name: String,
)
data class UserResponse(
val id: UUID,
val email: String,
val name: String,
val role: String,
val createdAt: Instant,
) {
companion object {
fun from(user: User): UserResponse = UserResponse(
id = user.id!!,
email = user.email,
name = user.name,
role = user.role,
createdAt = user.createdAt!!,
)
}
}
Repository
@Repository
interface UserRepository : JpaRepository<User, UUID> {
fun findByEmail(email: String): User?
fun existsByEmail(email: String): Boolean
}
Service
@Service
class UserService(
private val userRepository: UserRepository,
private val passwordEncoder: PasswordEncoder,
) {
@Transactional
fun createUser(request: CreateUserRequest): UserResponse {
if (userRepository.existsByEmail(request.email)) {
throw ConflictException("Email already registered")
}
val user = User(
email = request.email,
passwordHash = passwordEncoder.encode(request.password),
name = request.name,
)
return UserResponse.from(userRepository.save(user))
}
@Transactional(readOnly = true)
fun getUserById(id: UUID): UserResponse {
val user = userRepository.findById(id)
.orElseThrow { NotFoundException("User not found: $id") }
return UserResponse.from(user)
}
@Transactional
fun updateUser(id: UUID, request: UpdateUserRequest): UserResponse {
val user = userRepository.findById(id)
.orElseThrow { NotFoundException("User not found: $id") }
val updated = user.copy(
name = request.name ?: user.name,
email = request.email ?: user.email,
)
return UserResponse.from(userRepository.save(updated))
}
}
Controller
@RestController
@RequestMapping("/api")
class UserController(
private val userService: UserService,
) {
@PostMapping("/register")
fun register(
@Valid @RequestBody request: CreateUserRequest,
): ResponseEntity<UserResponse> {
val user = userService.createUser(request)
return ResponseEntity.status(HttpStatus.CREATED).body(user)
}
@GetMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN') or @userSecurity.isOwner(#id, authentication)")
fun getUser(@PathVariable id: UUID): ResponseEntity<UserResponse> {
return ResponseEntity.ok(userService.getUserById(id))
}
@GetMapping("/users")
@PreAuthorize("hasRole('ADMIN')")
fun getAllUsers(
@PageableDefault(size = 20) pageable: Pageable,
): ResponseEntity<Page<UserResponse>> {
return ResponseEntity.ok(userService.getAllUsers(pageable))
}
}
Exception Handling
class NotFoundException(message: String) : RuntimeException(message)
class UnauthorizedException(message: String) : RuntimeException(message)
class ConflictException(message: String) : RuntimeException(message)
data class ErrorResponse(
val timestamp: Instant = Instant.now(),
val status: Int,
val error: String,
val message: String,
val details: List<String>? = null,
)
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException::class)
fun handleNotFound(ex: NotFoundException) = ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ErrorResponse(status = 404, error = "NOT_FOUND", message = ex.message ?: "Not found"))
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidation(ex: MethodArgumentNotValidException): ResponseEntity<ErrorResponse> {
val details = ex.bindingResult.fieldErrors.map { "${it.field}: ${it.defaultMessage}" }
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse(status = 400, error = "VALIDATION_ERROR", message = "Validation failed", details = details))
}
}
Security Configuration
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig(
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
) {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain = http
.csrf { it.disable() }
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/api/register", "/api/login").permitAll()
.requestMatchers("/health/**", "/actuator/**").permitAll()
.anyRequest().authenticated()
}
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
.build()
@Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder(12)
}
Custom Annotation for Current User
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@AuthenticationPrincipal
annotation class CurrentUser
Coroutines Integration
Spring Boot supports Kotlin coroutines in controllers and services. Add these dependencies:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
Suspend functions in controllers are automatically handled by Spring WebFlux:
@GetMapping("/users/{id}")
suspend fun getUser(@PathVariable id: UUID): ResponseEntity<UserResponse> {
val user = userService.getUserById(id)
return ResponseEntity.ok(user)
}
For detailed coroutine, WebFlux, testing, and advanced security patterns, see references/patterns.md.
Commands
# Development
./gradlew bootRun
# Build
./gradlew build
# Test
./gradlew test
# Test with coverage
./gradlew test jacocoTestReport
# Format (with ktlint)
./gradlew ktlintFormat
# Lint
./gradlew ktlintCheck
# Build JAR
./gradlew bootJar
# Run JAR
java -jar build/libs/myproject-0.0.1-SNAPSHOT.jar
# Docker build
docker build -t myproject .
Best Practices
DO
- Use data classes for DTOs and entities
- Use Kotlin null safety features (
?.,?:,requireNotNull) - Use
@Transactional(readOnly = true)for read operations - Use constructor injection (default in Kotlin)
- Use
@Validfor request validation - Use Spring profiles for environment configuration
- Use
@field:annotation target for validation annotations on data class properties - Use coroutines for async operations where beneficial
DON'T
- Use
lateinit varfor dependencies (use constructor injection) - Ignore null safety in Spring Data repositories
- Use
!!operator without proper null checks - Mix reactive and blocking code in the same call chain
- Store secrets in configuration files
- Expose JPA entities directly in controller responses
References
For detailed patterns and examples, see:
- references/patterns.md -- WebFlux reactive patterns, JPA advanced patterns, testing, security advanced patterns
External References
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?