Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

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

DZone 's Guide to

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

Get to know Kotlin in this tutorial where we develop our own REST API in Kotlin using Spring Boot, Spring Data, and the H2 in-memory database.

· Java Zone ·
Free Resource

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 title

Running the application from IDE

Image title












Running the Integration Tests directly from IDE

Image title












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.), which requires 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.

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.

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 provideequals(), hashCode(),toString()componentN() functions, andcopy().

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.


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.

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-opreration-Tests

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

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:

{
    "_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 title

Running the Integration Tests directly from IDE


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


Topics:
kotlin ,spring boot ,rest api

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}