Build REST CRUD APIs with Kotlin, Spring Boot and MyBatis

Hello everyone, today we will learn how to develop REST-style CRUD APIs with Spring Boot, Kotlin, MyBatis, and H2 Database. You can download the source code from our Github repository.

What is MyBatis?

MyBatis is a persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

Some of the features of MyBatis:
  • In memory object filtering
  • Fortifies clustering and simultaneous access by other applications without loss of transaction integrity
  • Query Caching - Built-in support
  • Fortifies disconnected operations
  • Support for Remoting. Distributed Objects.

Technologies used :

  • Spring Boot 2.5.4
  • Spring  5.3.9
  • MyBatis 
  • Kotlin
  • Gradle

After completing this tutorial what we will build? 

We will build REST API  CRUD features: 
  1. GET - Fetch all User       :     /api/v1/users
  2. GET - Get User by ID     :     /api/v1/users/{id} 
  3. POST - Create User         :     /api/v1/users 
  4. PUT - Edit User Details   :     /api/v1/users/{id} 
  5. DELETE - Delete User    :     /api/v1/users/{id}

Project Directory:

Gradle Build(build.gradle.kts):

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("org.springframework.boot") version "2.5.4"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.5.21"
kotlin("plugin.spring") version "1.5.21"

group = ""
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {

dependencies {

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"

tasks.withType<Test> {

Database Setup:

We will create a table called users with a few simple columns. We can initialize a schema by creating a schema.sql file in the resources.
create table users
id integer not null,
firstName varchar(255) not null,
lastName varchar(255) not null,
emailId varchar(255) not null,
primary key(id)

Create User Model:


class User {
var id: Long = 0
var firstName: String? = null
var lastName: String? = null
var emailId: String? = null

constructor() {}
constructor(id: Long, firstName: String?, lastName: String?,
                                emailId: String?) : super() { = id
this.firstName = firstName
this.lastName = lastName
this.emailId = emailId

Create User MyBatis Repository:


import org.apache.ibatis.annotations.*

interface UserRepository {
@Select("select * from users")
fun findAll(): List<User?>?

@Select("SELECT * FROM users WHERE id = #{id}")
fun findById(id: Long): User?

@Delete("DELETE FROM users WHERE id = #{id}")
fun deleteById(id: Long): Int

"INSERT INTO users(id, firstName, lastName,emailId) " +
" VALUES (#{id}, #{firstName}, #{lastName}, #{emailId})"
fun insert(user: User?): Int

"Update users set firstName=#{firstName}, " +
" lastName=#{lastName}, emailId=#{emailId} where id=#{id}"
fun update(user: User?): Int

Create User Rest Controller:


import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.DeleteMapping
import java.util.HashMap

class UserController {
private val userRepository: UserRepository? = null

// get all users
val allUsers: List<User?>?
get() = userRepository!!.findAll()

// create user rest API
fun createUser(@RequestBody user: User): User? {
return if (userRepository!!.findById( == null) {
val id = userRepository.insert(user)
} else {
throw UserIdAlreadyExistException()

// get user by id rest api
fun getUserById(@PathVariable id: Long?): ResponseEntity<User> {
val user = userRepository!!.findById(id!!) ?: throw UserIdNotFoundException()
return ResponseEntity.ok(user)

// update user rest api
fun updateUser(
@PathVariable id: Long?,
@RequestBody userDetails: User
): ResponseEntity<User> {
if (userRepository!!.update(
id!!, userDetails.firstName,
userDetails.lastName, userDetails.emailId
) == 0
) {
throw UserIdNotFoundException()
return ResponseEntity.ok(userRepository.findById(id))

// delete user rest api
fun deleteUser(@PathVariable id: Long?): ResponseEntity<Map<String, Boolean>> {
val response: MutableMap<String, Boolean> = HashMap()
response["deleted"] = java.lang.Boolean.TRUE
return ResponseEntity.ok(response)

Create Custom Exception:



import java.lang.RuntimeException

class UserIdNotFoundException : RuntimeException("User Id Not Found")



import java.lang.RuntimeException

class UserIdAlreadyExistException : RuntimeException("User Id Already Exist")



import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.context.request.WebRequest
import org.springframework.http.ResponseEntity
import java.time.LocalDateTime
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ExceptionHandler
import java.lang.Exception

class GlobalExceptionHandler {
fun globalExceptionHandler(ex: Exception, request: WebRequest?):
ResponseEntity<CustomErrorResponse> {
val errors = CustomErrorResponse()
errors.timestamp =
errors.error = ex.message
errors.status = HttpStatus.NOT_FOUND.value()
return ResponseEntity<CustomErrorResponse>(errors, HttpStatus.NOT_FOUND)

fun globalExceptionHandler2(ex: Exception, request: WebRequest?):
ResponseEntity<CustomErrorResponse> {
val errors = CustomErrorResponse()
errors.timestamp =
errors.error = ex.message
errors.status = HttpStatus.INTERNAL_SERVER_ERROR.value()
return ResponseEntity<CustomErrorResponse>(errors,



import com.fasterxml.jackson.annotation.JsonFormat
import java.time.LocalDateTime

class CustomErrorResponse {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
var timestamp: LocalDateTime? = null
var status = 0
var error: String? = null

Spring Boot Main Class:


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

class KotlinSpringbootMybatisCrudExampleApplication

fun main(args: Array<String>) {

Run Spring Boot application:

Testing APIs using Postman

Create a User:

Fetch all the users:

Get user by id:

Update User:

Delete User:

Popular posts from this blog

Spring boot video streaming example-HTML5

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Spring Boot + Mockito simple application with 100% code coverage

Spring Boot + OpenCSV Export Data to CSV Example

Custom Exception Handling in Quarkus REST API

DataTable-Pagination example with Spring boot, jQuery and ajax

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Node JS mini projects with source code - free download

Spring boot web project free download:User Registration System

Java - Blowfish Encryption and decryption Example