Kotlin/Spring Boot 3.x patterns - use for backend services, REST APIs, dependency injection, controllers, and service layers
Provides Kotlin/Spring Boot 3.x patterns for backend services, including REST controllers, service layers, DTOs, and transaction management. Use when building or modifying Spring Boot applications with Kotlin.
/plugin marketplace add ashchupliak/dream-team/plugin install dream-team@dream-team-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
// build.gradle.kts
plugins {
kotlin("jvm") version "2.2.21"
kotlin("plugin.spring") version "2.2.21"
id("org.springframework.boot") version "3.5.7"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
}
data class Environment(
val id: UUID,
val name: String,
val status: EnvironmentStatus,
val createdAt: Instant,
val updatedAt: Instant?
)
enum class EnvironmentStatus {
PENDING, RUNNING, STOPPED, FAILED
}
@Service
class EnvironmentService(
private val repository: EnvironmentRepository,
private val computeClient: ComputeClient
) {
// Use NEVER propagation - let caller control transaction
@Transactional(propagation = Propagation.NEVER)
fun create(request: CreateEnvironmentRequest): Pair<EnvironmentResponse, Boolean> {
// Check for existing (idempotency)
repository.findByName(request.name)?.let {
return Pair(it.toResponse(), false) // existing
}
// Create new
val environment = Environment(
id = UUID.randomUUID(),
name = request.name,
status = EnvironmentStatus.PENDING,
createdAt = Instant.now(),
updatedAt = null
)
val saved = repository.save(environment)
return Pair(saved.toResponse(), true) // created
}
fun findById(id: UUID): Environment =
repository.findById(id)
?: throw ResourceNotFoundRestException("Environment", id)
fun findAll(): List<Environment> =
repository.findAll()
}
@RestController
class EnvironmentController(
private val service: EnvironmentService
) : EnvironmentApi {
override fun create(request: CreateEnvironmentRequest): ResponseEntity<EnvironmentResponse> {
val (result, isNew) = service.create(request)
return if (isNew) {
ResponseEntity.status(HttpStatus.CREATED).body(result)
} else {
ResponseEntity.ok(result)
}
}
override fun getById(id: UUID): ResponseEntity<EnvironmentResponse> =
ResponseEntity.ok(service.findById(id).toResponse())
override fun list(): ResponseEntity<List<EnvironmentResponse>> =
ResponseEntity.ok(service.findAll().map { it.toResponse() })
}
@Tag(name = "Environments", description = "Environment management")
interface EnvironmentApi {
@Operation(summary = "Create environment")
@ApiResponses(
ApiResponse(responseCode = "201", description = "Created"),
ApiResponse(responseCode = "200", description = "Already exists"),
ApiResponse(responseCode = "400", description = "Validation error")
)
@PostMapping("/api/v1/environments")
fun create(
@RequestBody @Valid request: CreateEnvironmentRequest
): ResponseEntity<EnvironmentResponse>
@Operation(summary = "Get environment by ID")
@GetMapping("/api/v1/environments/{id}")
fun getById(@PathVariable id: UUID): ResponseEntity<EnvironmentResponse>
@Operation(summary = "List all environments")
@GetMapping("/api/v1/environments")
fun list(): ResponseEntity<List<EnvironmentResponse>>
}
data class CreateEnvironmentRequest(
@field:NotBlank(message = "Name is required")
@field:Size(max = 100, message = "Name must be <= 100 chars")
val name: String,
@field:Size(max = 500)
val description: String? = null
)
data class EnvironmentResponse(
val id: UUID,
val name: String,
val status: String,
val createdAt: Instant
)
// Extension function for mapping
fun Environment.toResponse() = EnvironmentResponse(
id = id,
name = name,
status = status.name,
createdAt = createdAt
)
// Typed exceptions
throw ResourceNotFoundRestException("Environment", id)
throw ValidationRestException("Name cannot be empty")
throw ConflictRestException("Environment already exists")
// Global handler
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundRestException::class)
fun handleNotFound(ex: ResourceNotFoundRestException): ResponseEntity<ErrorResponse> =
ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ErrorResponse(ex.message ?: "Not found"))
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidation(ex: MethodArgumentNotValidException): ResponseEntity<ErrorResponse> {
val errors = ex.bindingResult.fieldErrors.map { "${it.field}: ${it.defaultMessage}" }
return ResponseEntity.badRequest()
.body(ErrorResponse("Validation failed", errors))
}
}
// Use ?.let for optional operations
user?.let { repository.save(it) }
// Use when for exhaustive matching
when (status) {
EnvironmentStatus.PENDING -> startEnvironment()
EnvironmentStatus.RUNNING -> return // already running
EnvironmentStatus.STOPPED -> restartEnvironment()
EnvironmentStatus.FAILED -> throw IllegalStateException("Cannot start failed env")
}
// Avoid !! - use alternatives
repository.findById(id).single() // throws if not exactly one
repository.findById(id).firstOrNull() // returns null if none
// Data class copy for immutable updates
val updated = environment.copy(
status = EnvironmentStatus.RUNNING,
updatedAt = Instant.now()
)
@ConfigurationProperties(prefix = "orca")
data class OrcaProperties(
val compute: ComputeProperties,
val timeouts: TimeoutProperties
) {
data class ComputeProperties(
val url: String,
val timeout: Duration = Duration.ofSeconds(30)
)
data class TimeoutProperties(
val creation: Duration = Duration.ofMinutes(5),
val termination: Duration = Duration.ofMinutes(2)
)
}
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.