Bootcamp
Kotlin + Spring Boot one-day bootcamp: scaffold, controllers, JPA, coroutines, tests, deploy.
Kotlin — Spring Boot bootcamp
EXAMPLE
# ===== 0-30 min: scaffold =====
# Spring Initializr (https://start.spring.io):
# Language: Kotlin, Build: Gradle Kotlin, JVM 21, Spring Web + Spring Data JPA + Postgres + Validation
curl https://start.spring.io/starter.zip -o demo.zip \
-d type=gradle-project-kotlin \
-d language=kotlin \
-d bootVersion=3.3.0 \
-d groupId=com.example \
-d artifactId=shop \
-d dependencies=web,data-jpa,postgresql,validation,actuator
unzip demo.zip && cd shop
# ===== 30-60 min: main + controller =====
# src/main/kotlin/com/example/shop/ShopApplication.kt
@SpringBootApplication
class ShopApplication
fun main(args: Array<String>) {
runApplication<ShopApplication>(*args)
}
# Controller:
@RestController
@RequestMapping("/api/users")
class UserController(private val service: UserService) {
@GetMapping("/{id}")
fun get(@PathVariable id: Long): UserDto =
service.findById(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun create(@Valid @RequestBody dto: CreateUserDto): UserDto = service.create(dto)
}
# ===== 60-120 min: entity + repository =====
@Entity
@Table(name = "users")
class User(
@Column(unique = true, nullable = false) var email: String,
@Column(nullable = false) var name: String,
) {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null
@CreationTimestamp var createdAt: Instant? = null
}
interface UserRepository : JpaRepository<User, Long> {
fun findByEmail(email: String): User?
}
# DTOs:
data class CreateUserDto(@field:Email val email: String, @field:NotBlank val name: String)
data class UserDto(val id: Long, val email: String, val name: String)
fun User.toDto() = UserDto(id!!, email, name)
# ===== 120-180 min: service =====
@Service
class UserService(private val repo: UserRepository) {
fun findById(id: Long): UserDto? = repo.findById(id).map { it.toDto() }.orElse(null)
@Transactional
fun create(dto: CreateUserDto): UserDto {
val user = User(email = dto.email, name = dto.name)
return repo.save(user).toDto()
}
}
# ===== 180-240 min: configuration =====
# src/main/resources/application.yml
spring:
application:
name: shop
datasource:
url: jdbc:postgresql://localhost:5432/shop
username: dev
password: dev
jpa:
hibernate:
ddl-auto: validate # never 'update' in production
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
# ===== 240-300 min: coroutines =====
# Spring 6 supports suspend functions in controllers:
@RestController
class AsyncController(private val client: WebClient) {
@GetMapping("/api/external")
suspend fun callExternal(): String {
return client.get().uri("/data").retrieve().awaitBody()
}
}
# Add reactor-kotlin + kotlinx-coroutines-reactor dependencies.
# ===== 300-360 min: tests =====
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest @Autowired constructor(val mvc: MockMvc) {
@Test
fun `creates user`() {
mvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""{"email":"a@x.io","name":"Alex"}"""))
.andExpect(status().isCreated)
.andExpect(jsonPath("\$.email").value("a@x.io"))
}
}
# ===== 360-420 min: build + deploy =====
./gradlew bootJar
java -jar build/libs/shop-0.0.1-SNAPSHOT.jar
# Docker:
./gradlew bootBuildImage # Spring's built-in image builder
docker run -p 8080:8080 shop:latest
# ===== Patterns to internalise =====
# - val by default; data class for DTOs
# - Constructor injection
# - @Transactional on writes
# - JPA migrations via Flyway / Liquibase
# - Actuator + Prometheus + Grafana for metrics
# ===== Pitfalls =====
# - ddl-auto: update in production
# - Mutable JPA entities + 'data class' (avoid; use plain class)
# - !! everywhere -> defeats null safety
# - Blocking calls inside suspend functions without Dispatchers.IO
Why it matters
A one-day Kotlin + Spring Boot bootcamp: scaffold, controller, entity, repository, service, suspend, tests, deploy. Same shape as Java Spring Boot but with Kotlin reflexes (val, data class, null-safe types, coroutines). Production-ready in a day.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…