Skip to main content

Kotlin Spring Boot

Introduction

Spring Boot has emerged as one of the most popular frameworks for developing backend applications in the Java ecosystem. When combined with Kotlin's concise syntax and powerful features, it creates an excellent platform for building robust, scalable, and maintainable backend services.

In this guide, we'll explore how to use Kotlin with Spring Boot to create efficient backend applications. We'll cover project setup, key concepts, and practical examples that demonstrate the power of this combination.

Why Kotlin Spring Boot?

Before diving into the technical details, let's understand why Kotlin Spring Boot is gaining popularity:

  • Concise code: Kotlin reduces boilerplate compared to Java
  • Null safety: Kotlin's type system helps prevent null pointer exceptions
  • Full interoperability: Seamlessly works with existing Java libraries
  • Spring's productivity: Spring Boot's auto-configuration and starters
  • Modern development: Coroutines for asynchronous programming

Setting Up a Kotlin Spring Boot Project

Using Spring Initializr

The easiest way to create a new Spring Boot project with Kotlin is using Spring Initializr.

  1. Visit https://start.spring.io/

  2. Select the following options:

    • Project: Gradle Kotlin
    • Language: Kotlin
    • Spring Boot version: (latest stable)
    • Group and Artifact: your preferred package name
    • Dependencies: Spring Web, Spring Data JPA (for database access)
  3. Click "Generate" to download the project template

Manual Project Setup

If you prefer setting up a project manually or adding Spring Boot to an existing Kotlin project, you need to add the following to your build.gradle.kts file:

kotlin
plugins {
id("org.springframework.boot") version "3.1.3"
id("io.spring.dependency-management") version "1.1.3"
kotlin("jvm") version "1.8.22"
kotlin("plugin.spring") version "1.8.22"
kotlin("plugin.jpa") version "1.8.22" // If using JPA
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")

// For database access with JPA
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2") // Using H2 database for development

testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Creating Your First Application

Let's create a simple REST API for a todo application. We'll start with a basic structure and build on it.

1. Main Application Class

kotlin
package com.example.kotlinspringboot

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class KotlinSpringBootApplication

fun main(args: Array<String>) {
runApplication<KotlinSpringBootApplication>(*args)
}

The @SpringBootApplication annotation combines several annotations:

  • @Configuration: Tags the class as a source of bean definitions
  • @EnableAutoConfiguration: Tells Spring Boot to configure beans based on classpath
  • @ComponentScan: Scans for components in the current package and sub-packages

2. Creating a Data Model

kotlin
package com.example.kotlinspringboot.model

import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.Id

@Entity
data class Todo(
@Id @GeneratedValue
val id: Long? = null,
var title: String,
var completed: Boolean = false
)

Here we're using:

  • data class for automatic getters, setters, and other utility methods
  • JPA annotations for persistence
  • Nullable types where appropriate (like id)

3. Repository Interface

kotlin
package com.example.kotlinspringboot.repository

import com.example.kotlinspringboot.model.Todo
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface TodoRepository : JpaRepository<Todo, Long> {
fun findByCompleted(completed: Boolean): List<Todo>
}

Spring Data JPA will automatically implement this interface, providing CRUD operations and custom query methods.

4. Service Layer

kotlin
package com.example.kotlinspringboot.service

import com.example.kotlinspringboot.model.Todo
import com.example.kotlinspringboot.repository.TodoRepository
import org.springframework.stereotype.Service

@Service
class TodoService(private val todoRepository: TodoRepository) {

fun getAllTodos(): List<Todo> = todoRepository.findAll()

fun getTodoById(id: Long): Todo = todoRepository.findById(id)
.orElseThrow { NoSuchElementException("Todo not found with id: $id") }

fun createTodo(todo: Todo): Todo = todoRepository.save(todo)

fun updateTodo(id: Long, todoDetails: Todo): Todo {
val todo = getTodoById(id)
todo.title = todoDetails.title
todo.completed = todoDetails.completed
return todoRepository.save(todo)
}

fun deleteTodo(id: Long) {
if (todoRepository.existsById(id)) {
todoRepository.deleteById(id)
} else {
throw NoSuchElementException("Todo not found with id: $id")
}
}

fun getCompletedTodos(): List<Todo> = todoRepository.findByCompleted(true)

fun getIncompleteTodos(): List<Todo> = todoRepository.findByCompleted(false)
}

Notice how Kotlin's concise syntax makes this service class much cleaner compared to Java.

5. REST Controller

kotlin
package com.example.kotlinspringboot.controller

import com.example.kotlinspringboot.model.Todo
import com.example.kotlinspringboot.service.TodoService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/todos")
class TodoController(private val todoService: TodoService) {

@GetMapping
fun getAllTodos(): List<Todo> = todoService.getAllTodos()

@GetMapping("/{id}")
fun getTodoById(@PathVariable id: Long): ResponseEntity<Todo> {
return try {
ResponseEntity.ok(todoService.getTodoById(id))
} catch (e: NoSuchElementException) {
ResponseEntity.notFound().build()
}
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun createTodo(@RequestBody todo: Todo): Todo = todoService.createTodo(todo)

@PutMapping("/{id}")
fun updateTodo(@PathVariable id: Long, @RequestBody todoDetails: Todo): ResponseEntity<Todo> {
return try {
ResponseEntity.ok(todoService.updateTodo(id, todoDetails))
} catch (e: NoSuchElementException) {
ResponseEntity.notFound().build()
}
}

@DeleteMapping("/{id}")
fun deleteTodo(@PathVariable id: Long): ResponseEntity<Unit> {
return try {
todoService.deleteTodo(id)
ResponseEntity.noContent().build()
} catch (e: NoSuchElementException) {
ResponseEntity.notFound().build()
}
}

@GetMapping("/completed")
fun getCompletedTodos(): List<Todo> = todoService.getCompletedTodos()

@GetMapping("/incomplete")
fun getIncompleteTodos(): List<Todo> = todoService.getIncompleteTodos()
}

6. Application Properties

Create a file named application.properties in the src/main/resources directory:

properties
# Database configuration
spring.datasource.url=jdbc:h2:mem:tododb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Enable H2 console (accessible at http://localhost:8080/h2-console)
spring.h2.console.enabled=true

# JPA/Hibernate configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

# Server configuration
server.port=8080

Running Your Application

To run your Kotlin Spring Boot application:

  1. From the command line:
bash
./gradlew bootRun
  1. Or, run the main application class from your IDE

Once started, your API will be available at http://localhost:8080/api/todos.

Testing Your API

You can test your Todo API using cURL, Postman, or any HTTP client. Here are some examples with cURL:

Create a new Todo

bash
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title": "Learn Kotlin Spring Boot", "completed": false}'

Expected Output:

json
{
"id": 1,
"title": "Learn Kotlin Spring Boot",
"completed": false
}

Get all Todos

bash
curl -X GET http://localhost:8080/api/todos

Expected Output:

json
[
{
"id": 1,
"title": "Learn Kotlin Spring Boot",
"completed": false
}
]

Update a Todo

bash
curl -X PUT http://localhost:8080/api/todos/1 \
-H "Content-Type: application/json" \
-d '{"title": "Learn Kotlin Spring Boot", "completed": true}'

Expected Output:

json
{
"id": 1,
"title": "Learn Kotlin Spring Boot",
"completed": true
}

Advanced Features

Exception Handling

Spring Boot allows centralized exception handling with @ControllerAdvice:

kotlin
package com.example.kotlinspringboot.exception

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import java.time.LocalDateTime

@ControllerAdvice
class GlobalExceptionHandler {

@ExceptionHandler(NoSuchElementException::class)
fun handleNotFoundException(ex: NoSuchElementException): ResponseEntity<ErrorResponse> {
val error = ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.message ?: "Resource not found",
LocalDateTime.now()
)
return ResponseEntity(error, HttpStatus.NOT_FOUND)
}

@ExceptionHandler(Exception::class)
fun handleGenericException(ex: Exception): ResponseEntity<ErrorResponse> {
val error = ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ex.message ?: "An unexpected error occurred",
LocalDateTime.now()
)
return ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR)
}
}

data class ErrorResponse(
val status: Int,
val message: String,
val timestamp: LocalDateTime
)

Using Kotlin Coroutines with Spring

Spring fully supports Kotlin's coroutines for asynchronous programming. Add the following dependency to your build.gradle.kts:

kotlin
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")

Here's how you can use coroutines in a Spring controller:

kotlin
@RestController
@RequestMapping("/api/async-todos")
class AsyncTodoController(private val todoService: TodoService) {

@GetMapping
suspend fun getAllTodos(): List<Todo> {
delay(100) // Simulating some async work
return todoService.getAllTodos()
}

@GetMapping("/flow")
fun getTodosAsFlow(): Flow<Todo> = flow {
todoService.getAllTodos().forEach {
delay(100) // Emit each item with a delay
emit(it)
}
}
}

Testing Kotlin Spring Boot Applications

Create test classes in the src/test/kotlin directory:

kotlin
package com.example.kotlinspringboot.controller

import com.example.kotlinspringboot.model.Todo
import com.example.kotlinspringboot.service.TodoService
import com.fasterxml.jackson.databind.ObjectMapper
import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when`
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*

@WebMvcTest(TodoController::class)
class TodoControllerTest {

@Autowired
private lateinit var mockMvc: MockMvc

@MockBean
private lateinit var todoService: TodoService

@Autowired
private lateinit var objectMapper: ObjectMapper

@Test
fun `should return all todos`() {
val todoList = listOf(
Todo(1, "Task 1", false),
Todo(2, "Task 2", true)
)

`when`(todoService.getAllTodos()).thenReturn(todoList)

mockMvc.perform(get("/api/todos"))
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$[0].id").value(1))
.andExpect(jsonPath("$[0].title").value("Task 1"))
.andExpect(jsonPath("$[1].id").value(2))
.andExpect(jsonPath("$[1].completed").value(true))
}

@Test
fun `should create a new todo`() {
val todo = Todo(null, "New Task", false)
val savedTodo = Todo(1, "New Task", false)

`when`(todoService.createTodo(todo)).thenReturn(savedTodo)

mockMvc.perform(
post("/api/todos")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(todo))
)
.andExpect(status().isCreated)
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.title").value("New Task"))
}
}

Real-World Example: Building a RESTful API with Authentication

In a real production environment, you would likely need authentication. Here's how to add JWT authentication to your Spring Boot application:

1. Add dependencies

In your build.gradle.kts:

kotlin
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")

2. Create user entities and repositories

kotlin
@Entity
data class User(
@Id @GeneratedValue
val id: Long? = null,

@Column(unique = true)
val username: String,

val password: String,

@ElementCollection(fetch = FetchType.EAGER)
val roles: Set<String> = setOf("ROLE_USER")
)

@Repository
interface UserRepository : JpaRepository<User, Long> {
fun findByUsername(username: String): User?
}

3. Create JWT utilities

kotlin
@Component
class JwtTokenUtil(@Value("\${jwt.secret}") private val secret: String) {

private val jwtParser = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secret.toByteArray()))
.build()

fun generateToken(username: String): String {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(Date())
.setExpiration(Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
.signWith(Keys.hmacShaKeyFor(secret.toByteArray()))
.compact()
}

fun validateToken(token: String): Boolean {
return try {
jwtParser.parseClaimsJws(token)
true
} catch (e: Exception) {
false
}
}

fun getUsernameFromToken(token: String): String {
return jwtParser.parseClaimsJws(token).body.subject
}
}

4. Configure Spring Security

kotlin
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val jwtTokenFilter: JwtTokenFilter
) {

@Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
return http
.csrf { it.disable() }
.authorizeHttpRequests {
it.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/todos/**").authenticated()
}
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter::class.java)
.build()
}
}

5. Create authentication controller

kotlin
data class LoginRequest(val username: String, val password: String)
data class LoginResponse(val token: String, val username: String)

@RestController
@RequestMapping("/api/auth")
class AuthController(
private val authenticationManager: AuthenticationManager,
private val jwtTokenUtil: JwtTokenUtil,
private val userRepository: UserRepository,
private val passwordEncoder: PasswordEncoder
) {

@PostMapping("/login")
fun login(@RequestBody request: LoginRequest): ResponseEntity<LoginResponse> {
try {
val authentication = authenticationManager.authenticate(
UsernamePasswordAuthenticationToken(request.username, request.password)
)

SecurityContextHolder.getContext().authentication = authentication
val token = jwtTokenUtil.generateToken(request.username)

return ResponseEntity.ok(LoginResponse(token, request.username))
} catch (e: Exception) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
}
}

@PostMapping("/register")
fun register(@RequestBody user: User): ResponseEntity<Any> {
if (userRepository.findByUsername(user.username) != null) {
return ResponseEntity.badRequest().body("Username already exists")
}

val newUser = User(
username = user.username,
password = passwordEncoder.encode(user.password)
)

userRepository.save(newUser)
return ResponseEntity.status(HttpStatus.CREATED).build()
}
}

This implementation provides a basic but secure authentication system for your API.

Summary

In this guide, you've learned how to:

  • Set up a Kotlin Spring Boot project
  • Create RESTful APIs with controllers, services, and repositories
  • Work with JPA for data persistence
  • Handle exceptions in a centralized way
  • Use Kotlin coroutines for asynchronous programming
  • Test your Spring Boot application
  • Implement JWT authentication

Kotlin and Spring Boot together provide an excellent platform for building modern, robust backend services. The combination of Kotlin's concise syntax and Spring's powerful features allows developers to create maintainable and efficient applications with less code.

Additional Resources

To continue learning Kotlin Spring Boot, check out these resources:

  1. Official Spring Documentation for Kotlin
  2. Kotlin for Server-Side Development
  3. Spring Boot Reference Documentation
  4. Spring Guides with Kotlin

Exercises

  1. Add pagination to the Todo API to return results in pages
  2. Implement filtering in the API (e.g., filter by title substring)
  3. Create a relation between Todo and a new Category entity
  4. Add validation to ensure Todo items have non-empty titles
  5. Implement a scheduled task that sends a daily summary of incomplete todos


If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)