Building a Spring Boot Web Application with Kotlin

Create a simple Spring Boot web application with Kotlin

Overview

This article creates a simple Spring Boot web application with Kotlin.

Create the Project

Use the curl command below to create an initial Spring Boot project.

curl https://start.spring.io/starter.tgz \
-d bootVersion=2.4.11 \
-d dependencies=web \
-d baseDir=spring-boot-hello-world \
-d groupId=com.devkuma \
-d artifactId=spring-boot-hello-world \
-d packageName=com.devkuma.hello \
-d applicationName=HelloApplication \
-d packaging=jar \
-d language=kotlin \
-d javaVersion=11 \
-d type=gradle-project | tar -xzvf -

Running this command creates a web project with Java 11 and Spring Boot 2.4.11.

The generated project has the following file structure.

.
├── HELP.md
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
    ├── main
    │   ├── kotlin
    │   │   └── com
    │   │       └── devkuma
    │   │           └── hello
    │   │               └── HelloApplication.kt
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── kotlin
            └── com
                └── devkuma
                    └── hello
                        └── HelloApplicationTests.kt

Check the Generated Project

build.gradle.kts

The generated build file uses the .kts extension, which means it is a Kotlin script file. (For reference, Java Gradle build files use the .gradle extension and are written in Groovy.)

/build.gradle.kts

// ... omitted ...

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

// ... omitted ...

Looking at the dependency libraries in the file, you can see that the Spring Framework (org.springframework.boot), Jackson for Kotlin (com.fasterxml.jackson.module), and Kotlin libraries (org.jetbrains.kotlin) have been added.

Application Context

/src/main/kotlin/com/devkuma/hello/controller/HelloController.kt

package com.devkuma.hello

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

@SpringBootApplication
class HelloApplication

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

You can confirm that the @SpringBootApplication annotation and the main function for running Spring Boot are present. (Overall, it looks similar to Java code.)

Create a Controller

Create a Controller file and write it as follows.

/src/main/kotlin/com/devkuma/hello/controller/HelloController.kt

package com.devkuma.hello.controller

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HelloController {

    @GetMapping("/hello")
    fun hello(): String {
        return "hello world"
    }
}

Create and run the test code.

/src/test/kotlin/com/devkuma/hello/controller/HelloControllerTests.kt

package com.devkuma.hello.controller

import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status


@WebMvcTest(HelloController::class)
class HelloControllerTests {

    @Autowired
    lateinit var mockMvc: MockMvc

    @Test
    @DisplayName("Hello")
    fun hello() {
        // when
        val resultActions: ResultActions = mockMvc.perform(get("/hello"))
            .andDo(print())

        // then
        resultActions
            .andExpect(status().is2xxSuccessful)
            .andExpect(content().string("hello world"))
            .andDo(print())
    }
}

Check that the test runs without errors.

To verify the application behavior directly, run the project and check it with the following curl command.

% curl localhost:8080/hello
hello world%

Create a Service

Create a Service file and write it as follows.

/src/main/kotlin/com/devkuma/hello/service/HelloService.kt

package com.devkuma.hello.service

import org.springframework.stereotype.Service

@Service
class HelloService {

    fun getHello(): String {
        return "hello service"
    }
}

Add helloService() to the Controller file as follows.

/src/main/kotlin/com/devkuma/hello/controller/HelloController.kt

package com.devkuma.hello.controller

import com.devkuma.hello.service.HelloService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HelloController(val helloService: HelloService) {

    // ... omitted ...

    @GetMapping("/hello-service")
    fun helloService(): String {
        return helloService.getHello()
    }
}

Add and run the helloService() test code as follows.

/src/test/kotlin/com/devkuma/hello/controller/HelloControllerTests.kt

package com.devkuma.hello.controller

import com.devkuma.hello.service.HelloService
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
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.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status


@WebMvcTest(HelloController::class)
class HelloControllerTests {

    @Autowired
    lateinit var mockMvc: MockMvc
    @MockBean
    lateinit var helloService: HelloService

    // ... omitted ...

    @Test
    @DisplayName("Hello Service")
    fun helloService() {
        // given
        given(helloService.getHello()).willReturn("hello service");

        // when
        val resultActions: ResultActions = mockMvc.perform(get("/hello-service"))
            .andDo(print())

        // then
        resultActions
            .andExpect(status().is2xxSuccessful)
            .andExpect(content().string("hello service"))
            .andDo(print())
    }
}

To verify the application behavior directly, run the project and check it with the following curl command.

% curl localhost:8080/hello-service
hello service%

Create a DTO

Create a DTO file and write it as follows.

/src/main/kotlin/com/devkuma/hello/dto/HelloDto.kt

package com.devkuma.hello.dto

class HelloDto(val greeting: String)

Add helloDto() to the Controller file as follows.

/src/main/kotlin/com/devkuma/hello/controller/HelloController.kt

@RestController
class HelloController(val helloService: HelloService) {

    // ... omitted ...

    @GetMapping("/hello-dto")
    fun helloDto(): HelloDto {
        return HelloDto("hello dto")
    }
}

Add and run the helloDto() test code as follows.

/src/test/kotlin/com/devkuma/hello/controller/HelloControllerTests.kt

package com.devkuma.hello.controller

import com.devkuma.hello.service.HelloService
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
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.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*


@WebMvcTest(HelloController::class)
class HelloControllerTests {

    // ... omitted ...

    @Test
    @DisplayName("Hello DTO")
    fun helloDto() {

        // when
        val resultActions: ResultActions = mockMvc.perform(get("/hello-dto"))
            .andDo(print())

        // then
        resultActions
            .andExpect(status().is2xxSuccessful)
            .andExpect(jsonPath("greeting").value("hello dto"))
            .andDo(print())
    }
}

To verify the application behavior directly, run the project and check it with the following curl command.

% curl localhost:8080/hello-dto
{"greeting":"hello dto"}%

More