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.
-
Visit https://start.spring.io/
-
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)
-
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:
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
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
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
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
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
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:
# 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:
- From the command line:
./gradlew bootRun
- 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
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title": "Learn Kotlin Spring Boot", "completed": false}'
Expected Output:
{
"id": 1,
"title": "Learn Kotlin Spring Boot",
"completed": false
}
Get all Todos
curl -X GET http://localhost:8080/api/todos
Expected Output:
[
{
"id": 1,
"title": "Learn Kotlin Spring Boot",
"completed": false
}
]
Update a Todo
curl -X PUT http://localhost:8080/api/todos/1 \
-H "Content-Type: application/json" \
-d '{"title": "Learn Kotlin Spring Boot", "completed": true}'
Expected Output:
{
"id": 1,
"title": "Learn Kotlin Spring Boot",
"completed": true
}
Advanced Features
Exception Handling
Spring Boot allows centralized exception handling with @ControllerAdvice
:
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
:
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:
@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:
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
:
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
@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
@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
@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
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:
- Official Spring Documentation for Kotlin
- Kotlin for Server-Side Development
- Spring Boot Reference Documentation
- Spring Guides with Kotlin
Exercises
- Add pagination to the Todo API to return results in pages
- Implement filtering in the API (e.g., filter by title substring)
- Create a relation between Todo and a new Category entity
- Add validation to ensure Todo items have non-empty titles
- 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! :)