Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / Kotlin

Kotlin: How to Implement a REST API With Spring Boot, Spring Data, and H2 DB

Rate me:
Please Sign up or sign in to vote.
4.80/5 (3 votes)
23 Nov 2018CPOL6 min read 9.5K   4  
Developing REST API in Kotlin and SpringBoot

Introduction

In this article, we are going to talk about Kotlin. I have developed a very simple REST API in Kotlin using Spring Boot, Spring Data, and the H2 in-memory DB.

Kotlin and Spring Boot work well together.

You will notice in the Code Walkthrough section that there is NO controller and NO service class in the project. That's magic of Spring's @RepositoryRestResource, which is further explained below.

I have no experience in Kotlin, but what I read and saw on Kotlin code in GitHub projects was definitely worth exploring.

An important question you might ask is: why Kotlin?

Why Kotlin?

  • Kotlin compiles to bytecode, so it can perform just as well as Java.
  • Kotlin is more succinct than Java.
  • Kotlin's Data classes are more concise than Java's value classes.
  • Classes are final by default, which corresponds to Effective Java Item 17 — you would need to explicitly put open if you want to make a class inheritable.
  • Abstract classes are open by default.
  • One of Kotlin’s key features is null-safety, which cleanly deals with null values at compile time rather than bumping into the famous NullPointerException at runtime.
  • Primary constructor vs. secondary constructors — if you need more than one constructor, then only you would go for secondary. Otherwise, most of the Kotlin class would have a primary constructor.
  • Kotlin can also be used as a scripting language.
  • Kotlin and Java are inter-operable, so it's easy to do a trial of Kotlin on just a small part of your codebase.
  • Kotlin uses aggressive type inference to determine the types of values and expressions for which type has been left unstated. This reduces language verbosity relative to Java.
  • Kotlin is fully supported by Google for use with their Android operating system.

The two points below reference Wikipedia:

  • "According to JetBrains blog, Kotlin is used by Amazon Web Services, Pinterest, Coursera, Netflix, Uber, and others. Corda, a distributed ledger developed by a consortium of well-known banks (such as Goldman Sachs, Wells Fargo, J.P. Morgan, Deutsche Bank, UBS, HSBC, BNP Paribas, Société Générale), has over 90 percent of Kotlin in its codebase."
  • According to Google, Kotlin has already been adopted by several major developers — Expedia, Flipboard, Pinterest, Square, and others — for their Android production apps.

Code Walkthrough

The project code for this example can be found on my Kotlin Github Repo, demonstrating a simple REST API using Kotlin and Spring Boot

Clone - https://github.com/BeTheCodeWithYou/SpringBoot-Kotlin.git

Project Structure

Image 1Image 2

Running the Application from IDE

Image 3

Running the Integration Tests Directly from IDE

Image 4

Understanding build.gradle

buildscript {
ext {
kotlinVersion = '1.2.41'
springBootVersion = '2.0.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}")
}
}
apply plugin: 'base'
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'kotlin-jpa'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.xp.springboot.restapi.kotlin'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}

compileTestKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}

repositories {
mavenCentral()
}

dependencies {

compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-web')
compile('com.fasterxml.jackson.module:jackson-module-kotlin')
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compile("org.jetbrains.kotlin:kotlin-reflect")
runtime("com.h2database:h2")

testCompile("org.jsmart:zerocode-rest-bdd:1.2.11")
testCompile('org.springframework.boot:spring-boot-starter-test')
}

task integrationTests (type: Test) {

delete '/target/'
systemProperty 'zerocode.junit', 'gen-smart-charts-csv-reports'
include 'integrationtests/TestGetOperations.class'
include 'integrationtests/TestPatchOperations.class'
include 'integrationtests/TestPostOperations.class'

testLogging {

showStandardStreams = true
}
}
  • org.jetbrains.kotlin:kotlin-gradle-plugin compiles Kotlin sources and modules.
  • org.jetbrains.kotlin:kotlin-allopen — this is the interesting part here. In Kotlin, by default, all classes are final.

Now, in order to make a class inheritable, you have to annotate with an open keyword. And, the problem is that a lot of other libraries, like Spring, test libraries (Mockito, etc.), require classes and methods to become non-final. In Spring, such classes mainly include @Configuration classes and @Bean methods.

The rule here is simple; you need to annotate the @Configuration and @Bean methods to mark them open, but this approach is tedious and error-prone, hence Kotlin has come up with the compiler plugin to automate this process through this dependency using org.jetbrains.kotlin:kotlin-noarg and apply plugin: 'kotlin-jpa'.

In order to be able to use Kotlin immutable classes, we need to enable the Kotlin JPA plugin. It will generate no-arg constructors for any class annotated with @Entity, apply plugin: 'kotlin'.

To target the JVM, the Kotlin plugin needs to be applied: apply plugin: 'kotlin-spring'. This is required for Spring Kotlin integration.

  • testCompile("org.jsmart:zerocode-rest-bdd:1.2.11") - Integration Tests library dependency.

Compiler Options

Spring nullability annotations provide null-safety for the whole Spring API to Kotlin developers, with the advantage of dealing with null-related issues at compile time. This feature can be enabled by adding the -Xjsr305 compiler flag with the strict options. And, it also configures the Kotlin compiler to generate Java 8 bytecode.

compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
  • compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8") is the Java 8 variant of the Kotlin standard library.
  • compile('com.fasterxml.jackson.module:jackson-module-kotlin') adds support for serialization/deserialization of Kotlin and data classes.
  • compile("org.jetbrains.kotlin:kotlin-reflect") is the Kotlin reflection library.

The Spring Boot Gradle plugin automatically uses the Kotlin version declared on the Kotlin Gradle plugin, hence the version is not defined explicitly on the dependencies section.

All remaining entries are self-explanatory.

Kotlin Code

Spring Boot Application

/src/main/kotlin/com.xp.springboot.kotlin.SpringBootKotlinRestApiApplication.kt

Notice the missing semicolon above. You need to open and close braces of the class only if you have the @Bean. Otherwise, a class with name is required. Runapplication is a top-level function.

Java
package com.xp.springboot.kotlin

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.context.annotation.Bean
import com.xp.springboot.kotlin.repository.ParkRunnerRepository
import org.springframework.boot.CommandLineRunner
import com.xp.springboot.kotlin.model.ParkRunner
import org.springframework.boot.ApplicationRunner
import org.springframework.boot.SpringApplication
import org.springframework.boot.context.ApplicationPidFileWriter

@SpringBootApplication
@ComponentScan(basePackages = arrayOf("com.xp.springboot.*"))
@EnableJpaRepositories("com.xp.springboot.*")
@EntityScan("com.xp.springboot.*")
class SpringBootKotlinRestApiApplication {

@Bean
fun run(repository : ParkRunnerRepository) = ApplicationRunner {

repository.save(ParkRunner(firstName = "NEERAJ", lastName="SIDHAYE", gender="M",
totalRuns="170", runningClub="RUNWAY"))
}
}

fun main(args: Array<String>) {

runApplication<SpringBootKotlinRestApiApplication>(*args)
}

fun start() {

runApplication<SpringBootKotlinRestApiApplication>()
}

Creating a Data Class

Then, we create our model by using Kotlin data classes, which are designed to hold data and automatically provide equals(), hashCode(),toString(), componentN() functions, and copy().

Also, you could define multiple entities in the same data class. Var is similar to the general variable and is known as a mutable variable in Kotlin, which can be assigned multiple times.

There is another type: val, which is like constant variable and is known as immutable in Kotlin and can be initialized only a single time. Moreover, val is read-only, and you are not allowed to explicitly write to val.

Java
package com.xp.springboot.kotlin.model

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Table

@Entity
@Table(name="PARK_RUNNER")
data class ParkRunner (

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var parkRunId: Long = -1,

@Column(name = "firstName")
var firstName: String = "",

@Column(name = "lastName")
var lastName: String = "",

@Column(name = "gender")
var gender: String = "",

@Column(name = "runningClub")
var runningClub: String = "",

@Column(name = "totalRuns")
var totalRuns: String = "0"

){ }

Creating a Repository

Yes, there is just the one-liner to define the repository interface with the Spring Data curd repository. The interesting Spring annotation here is RepositoryRestResource. This comes by adding the spring-boot-starter-data-rest dependency.

Java
package com.xp.springboot.kotlin.repository

import org.springframework.data.repository.CrudRepository
import com.xp.springboot.kotlin.model.ParkRunner
import org.springframework.data.rest.core.annotation.RepositoryRestResource

@RepositoryRestResource(collectionResourceRel = "runners", path = "runners")
interface ParkRunnerRepository : CrudRepository <ParkRunner, Long >{
}

If you noticed, there is no Controller and no Service. This project is exposing the following REST endpoints with HATEOAS enabled.

  • GET - http://localhost:8080/parkrun/runners
  • POST - http://localhost:8080/parkrun/runners
  • GET - http://localhost:8080/parkrun/runners/2
  • DELETE - http://localhost:8080/parkrun/runners/1

It's the magic of @RepositoryRestResource.

ApplycollectionResourceRel to define custom resource label, or else, the annotation will use the default as per the model class name (/parkRunners).

At runtime, Spring Data REST will create an implementation of this interface automatically. Then, it will use the @RepositoryRestResource annotation to direct Spring MVC and create RESTful endpoints at /parkRunners.

Integration Test Cases

GET-operation-Tests

Java
package integrationtests

import org.jsmart.zerocode.core.domain.TargetEnv
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner
import org.junit.runner.RunWith
import org.junit.Test
import org.jsmart.zerocode.core.domain.JsonTestCase
import org.jsmart.zerocode.core.domain.EnvProperty

@TargetEnv("application_host.properties")
@RunWith(ZeroCodeUnitRunner::class)
class TestGetOperations {

@Test
@JsonTestCase("integration_tests/get/get_all_runners.json")
fun `get all runners`() {
}
}

POST-operation-Tests

Java
package integrationtests

import org.jsmart.zerocode.core.domain.JsonTestCase
import org.jsmart.zerocode.core.domain.TargetEnv
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner
import org.junit.runner.RunWith
import org.junit.Test

@TargetEnv("application_host.properties")
@RunWith(ZeroCodeUnitRunner::class)
class TestPostOperations {

@Test
@JsonTestCase("integration_tests/post/create_new_runner.json")
fun `post create runner`() {

}

}

PATCH-Operation-Tests

package integrationtests

import org.junit.runner.RunWith
import org.jsmart.zerocode.core.runner.ZeroCodeUnitRunner
import org.jsmart.zerocode.core.domain.TargetEnv
import org.jsmart.zerocode.core.domain.JsonTestCase
import org.junit.Test

@TargetEnv("application_host.properties")
@RunWith(ZeroCodeUnitRunner::class)
class TestPatchOperations {

@Test
@JsonTestCase("integration_tests/patch/patch_runner_profile.json")
fun `patch runner profile`() {

}
}

Building the Application

gradle clean build -x test 

will generate spring boot application jar into the /build/libs/.

Integration tests are skipped using -x test because integration test would expect application to be running, so we will first just build the app, then run the app, and then execute integration tests against the local running app instance.

Running the Application

Once you have the jar ready after the gradle clean build, run the app using:

java -jar SpringBootKotlinRestAPI-0.0.1-SNAPSHOT.jar and hit the URL http://localhost:8080/parkrun directly on the browser.

Here is a look at the response:

JavaScript
{
    "_links": {
        "runners": {
            "href": "http://localhost:8080/parkrun/runners"
        },
        "profile": {
            "href": "http://localhost:8080/parkrun/profile"
        }
    }
}

Explore other end points available with this simple Kotlin Rest API:

  • GET - http://localhost:8080/parkrun/runners
  • POST - http://localhost:8080/parkrun/runners
  • GET - http://localhost:8080/parkrun/runners/2>
  • PATCH - http://localhost:8080/parkrun/runners/1

Running the Integration Test Cases

gradle integrationTests 

This gradle task will run all the integration tests against the local running instance of the application jar.

Integration Test Reports

You could find the integration test and logs, under the folder /target/.

Image 5Image 6

Hope you find this article useful for developing Spring Boot REST API with Kotlin along with Integration Test cases. Happy coding!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --