internal/skills/content/spring-boot-kotlin/SKILL.md
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.
npx skillsauth add ar4mirez/samuel spring-boot-kotlinInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Spring Boot 3.x, Kotlin 1.9+, REST APIs, Microservices Use with:
.claude/skills/kotlin-guide/SKILL.mdfor base Kotlin patterns
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
@Autowired needed)lateinit var for dependencies -- constructor injection is idiomatic@Transactional(readOnly = true) for all read-only operations@Valid on all request body parameters for automatic validationapplication-{profile}.yml) for environment config${ENV_VAR:default} syntax@ConfigurationProperties data classes for type-safe configuration@field: annotation target for validationopen modifier or plugin.spring (auto-opens @Component classes)data class for JPA entities with plugin.jpa (auto-generates no-arg constructors)val for entity properties, use copy() for updatesval id: UUID? = null for new entities@CreationTimestamp and @UpdateTimestamp for audit fieldsmodel/entity/, DTOs in model/dto/@Service@Transactional for write operations, @Transactional(readOnly = true) for readsNotFoundException, ConflictException)T? for nullable finds@RestController with @RequestMapping for base pathsResponseEntity<T> with explicit status codes@Valid @RequestBody for all incoming request bodies@PreAuthorize for method-level security@PageableDefault for pagination parameters@SpringBootApplication
class MyProjectApplication
fun main(args: Array<String>) {
runApplication<MyProjectApplication>(*args)
}
@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
@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,
)
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
interface UserRepository : JpaRepository<User, UUID> {
fun findByEmail(email: String): User?
fun existsByEmail(email: String): Boolean
}
@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))
}
}
@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))
}
}
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))
}
}
@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)
}
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@AuthenticationPrincipal
annotation class CurrentUser
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.
# 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 .
?., ?:, requireNotNull)@Transactional(readOnly = true) for read operations@Valid for request validation@field: annotation target for validation annotations on data class propertieslateinit var for dependencies (use constructor injection)!! operator without proper null checksFor detailed patterns and examples, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.