Kotlin Microservice With Spring Boot Tutorial
We walk through building a simple microservice using Kotlin and Spring Boot. It's the new hotness!
Join the DZone community and get the full member experience.
Join For FreeYou may find many examples of microservices built with Spring Boot on my blog, but most of them are written in Java. With the rise in popularity of the Kotlin language, it is more often used with Spring Boot for building backend services. Starting with version 5, the Spring framework introduced first-class support for Kotlin. In this article, I’m going to show you an example of microservices built with Kotlin and Spring Boot 2. I’ll describe some interesting features of Spring Boot, which can be treated as a set of good practices when building backend, REST-based microservices.
1. Configuration and Dependencies
To use Kotlin in your Maven project you have to include plugin kotlin-maven-plugin
, and /src/main/kotlin
, /src/test/kotlin
directories to the build configuration. We will also set -Xjsr305
compiler flag to strict
. This option is responsible for checking support for JSR-305 annotations (for example @NotNull
annotation).
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
We should also include some core Kotlin libraries like kotlin-stdlib-jdk8
and kotlin-reflect
. They are provided by default for a Kotlin project on start.spring.io. For REST-based applications, you will also need the Jackson library for JSON serialization/deserialization. Of course, we have to include Spring starters for web applications together with Actuator, which is responsible for providing management endpoints.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
We use the latest stable version of Spring Boot with Kotlin 1.2.71
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.2.71</kotlin.version>
</properties>
2. Building the Application
Let’s begin with the basics. If you are familiar with Spring Boot and Java, the biggest difference is in the main class declaration. You will call runApplication
method outside the Spring Boot application class. The main class, the same as in Java, is annotated with @SpringBootApplication
.
@SpringBootApplication
class SampleSpringKotlinMicroserviceApplication
fun main(args: Array<String>) {
runApplication<SampleSpringKotlinMicroserviceApplication>(*args)
}
Our sample application is very simple. It exposes some REST endpoints providing CRUD operations for the model object. Even with this fragment of code, illustrating controller implementation, you can see some nice Kotlin features. We may use a shortened function declaration with an inferred return type. The annotation @PathVariable
does not require any arguments. The input parameter name is considered to be the same as the variable name. Of course, we are using the same annotations as with Java. In Kotlin, every property declared as having non-null type must be initialized in the constructor. So, if you are initializing it using dependency injection it has to declared as lateinit
. Here’s the implementation of PersonController
.
@RestController
@RequestMapping("/persons")
class PersonController {
@Autowired
lateinit var repository: PersonRepository
@GetMapping("/{id}")
fun findById(@PathVariable id: Int): Person? = repository.findById(id)
@GetMapping
fun findAll(): List<Person> = repository.findAll()
@PostMapping
fun add(@RequestBody person: Person): Person = repository.save(person)
@PutMapping
fun update(@RequestBody person: Person): Person = repository.update(person)
@DeleteMapping("/{id}")
fun remove(@PathVariable id: Int): Boolean = repository.removeById(id)
}
Kotlin automatically generates getters and setters for class properties declared as var
. Also if you declare the model as a data class it generates equals
, hashCode
, and toString
methods. The declaration of our model class Person
is very concise as shown below.
data class Person(var id: Int, var name: String, var age: Int, var gender: Gender)
I have implemented my own in-memory repository class. I use Kotlin extensions for manipulating the list of elements. This built-in Kotlin feature is similar to Java streams, with the difference that you don’t have to perform any conversion between Collection
and Stream
.
@Repository
class PersonRepository {
val persons: MutableList<Person> = ArrayList()
fun findById(id: Int): Person? {
return persons.singleOrNull { it.id == id }
}
fun findAll(): List<Person> {
return persons
}
fun save(person: Person): Person {
person.id = persons.size + 1
persons.add(person)
return person
}
fun update(person: Person): Person {
val index = persons.indexOf(person)
persons[index] = person
return person
}
fun removeById(id: Int): Boolean {
return persons.removeIf { it.id == id }
}
}
The sample application source code is available on the GitHub in repository, here.
3. Enabling Actuator Endpoints
Since we have already included Spring Boot starter with Actuator in the application code, we can take advantage of its production-ready features. Spring Boot Actuator gives you very powerful tools for monitoring and managing your apps. You can provide advanced health checks, info endpoints, or send metrics to numerous monitoring systems like InfluxDB. After including Actuator artifacts, the only thing we have to do is to enable all its endpoint for our application via HTTP.
management.endpoints.web.exposure.include: '*'
We can customize Actuator endpoints to provide more details about our app. A good practice is to expose information about the version and git commit to the info
endpoint. As usual, Spring Boot provides auto-configuration for such features, so the only thing we need to do is to include some Maven plugins to the build configuration in pom.xml
. The goal, build-info
, is set for spring-boot-maven-plugin
and forces it to generate a properties file with basic information about the version. The file is located in the directory, META-INF/build-info.properties
. The plugin git-commit-id-plugin
will generate a git.properties
file in the root directory.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<configuration>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>
</plugin>
Now you should just build your application using mvn clean install
command and then run it.
$ java -jar target\sample-spring-kotlin-microservice-1.0-SNAPSHOT.jar
The info
endpoint is available under address http://localhost:8080/actuator/info. It exposes some interesting information for us.
{
"git":{
"commit":{
"time":"2019-01-14T16:20:31Z",
"id":"f7cb437"
},
"branch":"master"
},
"build":{
"version":"1.0-SNAPSHOT",
"artifact":"sample-spring-kotlin-microservice",
"name":"sample-spring-kotlin-microservice",
"group":"pl.piomin.services",
"time":"2019-01-15T09:18:48.836Z"
}
}
4. Enabling API Documentation
Build info and git properties may be easily injected into the application code. It can be useful in some cases. One of those cases is if you have enabled auto-generated API documentation. The most popular tools for this is Swagger. You can easily integrate Swagger 2 with Spring Boot using the SpringFox Swagger project. First, you need to include the following dependencies to your pom.xml
.
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
Then, you should enable Swagger by annotating the configuration class with @EnableSwagger2
. The required information is available inside the beans, BuildProperties
and GitProperties
. We just have to inject them into the Swagger configuration class as shown below. We set them as optional to prevent application startup failure in case they are not present on the class path.
@Configuration
@EnableSwagger2
class SwaggerConfig {
@Autowired
lateinit var build: Optional<BuildProperties>
@Autowired
lateinit var git: Optional<GitProperties>
@Bean
fun api(): Docket {
var version = "1.0"
if (build.isPresent && git.isPresent) {
var buildInfo = build.get()
var gitInfo = git.get()
version = "${buildInfo.version}-${gitInfo.shortCommitId}-${gitInfo.branch}"
}
return Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo(version))
.select()
.apis(RequestHandlerSelectors.any())
.paths{ it.equals("/persons")}
.build()
.useDefaultResponseMessages(false)
.forCodeGeneration(true)
}
@Bean
fun uiConfig(): UiConfiguration {
return UiConfiguration(java.lang.Boolean.TRUE, java.lang.Boolean.FALSE, 1, 1, ModelRendering.MODEL, java.lang.Boolean.FALSE, DocExpansion.LIST, java.lang.Boolean.FALSE, null, OperationsSorter.ALPHA, java.lang.Boolean.FALSE, TagsSorter.ALPHA, UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS, null)
}
private fun apiInfo(version: String): ApiInfo {
return ApiInfoBuilder()
.title("API - Person Service")
.description("Persons Management")
.version(version)
.build()
}
}
The documentation is available under the context path,/swagger-ui.html
. Besides API documentation, it displays the information about the application version, git commit id, and branch name.
5. Choosing Your App Server
Spring Boot Web can be run on three different embedded servers: Tomcat, Jetty, or Undertow. By default, it uses Tomcat. To change the default server, you just need to include the suitable Spring Boot starter and exclude spring-boot-starter-tomcat
. A good practice may be to enable switching between servers during application build. You can achieve it by declaring Maven profiles as shown below.
<profiles>
<profile>
<id>tomcat</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>jetty</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>undertow</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
Now, if you would like to enable servers other than Tomcat for your application, you should activate the appropriate profile during the Maven build.
$ mvn clean install -Pjetty
Conclusion
The development of microservices using Kotlin and Spring Boot is nice and simple. Based on this sample application, I have introduced the main Spring Boot features for Kotlin. I also described some good practices you may apply to your microservices when building them using Spring Boot and Kotlin. You can compare the described approach with some other micro-frameworks used with Kotlin, for example, Ktor, described in one of my previous articles: Kotlin Microservices with Ktor.
Opinions expressed by DZone contributors are their own.
Comments