Skip to content

Create a Simple REST API with Kotlin and Spring Boot

Posted on:February 27, 2024 at 06:20 PM

Table of Contents

Open Table of Contents

Intro

Lately, I have been trying out Kotlin, not for Android applications development, but for back-end development. Therefore, like every other Java/C# developer, I decided to learn the language by converting a simple Java based API to Kotlin. For the sake of this article, we will use the code snippets from Contract First Approach in API Design With OpenAPI and Java 17 + Spring Boot and build up knowledge gained from, Build a REST API with Spring boot.

A Short Background on Kotlin

As you may already know, Kotlin is built by JetBrains, the company that brought us super cool IDE’s like IntelliJ, PyCharm, GoLang, Webstorm, etc. Well, guilty as charged, I only use JetBrains IDEs. What you may not know is that Kotlin is a statically typed, high level programming language with type inference. What does type inference mean? Let us use an example.

Java Code

// Explicit type declaration is very important
int num = 1; 
List<String> listOfStrings = new ArrayList<>();
listOfStrings.add("Apples");
listOfStrings.add("Mangoes");
listOfStrings.add("Pineapples");

var num = 1; // This is also acceptable in Java
Kotlin Code

// Kotlin automatically infers the data type, if data type is missing
var num = 1 
val listOfStrings = listOf("Apples", "Mangoes", "Pineapples")

var num: Int = 1 // However, you could also declare the type

Java Folder Structure

In its simplest form, a typical Java project usually has a folder structure shown below. However, as your project grows, separating concerns within packages, such as creating a service package for your services would be the ideal way to structure your code.

├── employeedemo
|     ├── src
|        ├── main
|        |   ├── java/com/employeedemo
|        |   ├── EmployeeController.java
|        |   ├── EmployeeDemoApplication.java
|        |   ├── EmployeeEntity.java
|        |   ├── EmployeeRepository.java
|        |   └── EmployeeService.java
|        └──  resources
|            ├── openApi
|            |   └── employee-spec.yaml   
|            └── dev-properties.yaml
└──  pom.xml

Kotlin Project Initialization

Similar to a Java + Spring Boot project, Kotlin + Spring Boot project uses Spring Initialzr for initialization. Therefore, open Spring Initializr on your browser, select:

Then add other dependencies as you see fit.

Kotlin Folder Structure

├── employeedemo
|     ├── src
|        ├── main
|        |   ├── kotlin/com/transformation/employeekotlin
|        |   ├── EmployeeController.kt
|        |   ├── EmployeeKotlinApplication.kt
|        |   ├── EmployeeEntity.kt
|        |   ├── EmployeeRepository.kt
|        |   └── EmployeeService.kt
|        └──  resources
|            ├── openApi
|            |   └── employee-spec.yaml   
|            └── dev-properties.yaml
└──  pom.xml

The .kt file extension aside, you will note that the difference between the Java and Kotlin folder structures is minimal.

Show me the Code!!

Employee Entity

// EmployeeEntity.java class

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Data
@Table(name = "employee")
public class EmployeeEntity {
    @Id
    private Long employeeId;
    private String firstName;
    private String lastName;
}

Notes:

// EmployeeEntity.kt class

@Entity
@Table(name="employee")
class EmployeeEntity(
    @Id internal var employeeId: Long?,
    internal var firstname: String?,
    internal var lastName: String?
)

Notes:

Employee Repository

// EmployeeRepository.java class

public interface EmployeeRepository extends CrudRepository<EmployeeEntity, Long> {
    List<EmployeeEntity> findByEmployeeId(Long employeeId);
}

Notes:

// EmployeeRepository.kt

interface EmployeeRepository: CrudRepository<EmployeeEntity, Long> {
    fun findByEmployeeId(employeeId: Long): List<EmployeeEntity>
}

Notes:

Employee Service

// EmployeeService.java

@Service
public class EmployeeService {
    private final EmployeeRepository employeeRepository; // a.

    public EmployeeService(EmployeeRepository employeeRepository) { // b.
        this.employeeRepository = employeeRepository;
    }

    public void postEmployee(EmployeeEntity employeeEntity) {
        employeeRepository.save(employeeEntity); 
    }

    public List<EmployeeEntity> getEmployeeById(Long employeeId) {
        return employeeRepository.findByEmployeeId(employeeId); // c.
    }
}

Notes:

// EmployeeService.kt

@Service
class EmployeeService(private val employeeRepository: EmployeeRepository) { // a.
    fun postEmployee(employeeEntity: EmployeeEntity) = employeeRepository.save(employeeEntity) // b.

    fun getEmployeeById(employeeId: Long): List<EmployeeEntity>{ // c.
        return employeeRepository.findByEmployeeId(employeeId)
    }
}

Notes:

Employee Controller

// EmployeeController.java

public class EmployeeController implements EmployeeApi { // a.

    private final EmployeeService employeeService;

    public EmployeeController(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @Override
    public ResponseEntity<HttpCreatedResponse> createEmployee(EmployeeData employeeData) {
        employeeService.postEmployee(toEmployeeEntity(employeeData)); // b.
        return new ResponseEntity<>(HttpStatus.CREATED);
    }

    @Override
    public ResponseEntity<List<EmployeeData>> getEmployeeById(String employeeId) {
        List<EmployeeEntity> list = employeeService.getEmployeeById(Long.parseLong(employeeId));
        List<EmployeeData> listData = list.stream().map(data -> toEmployeeData(data)).collect(Collectors.toList()); // c.
        log.info("=====> Fetched an employee {}", listData.stream().toList());
        return new ResponseEntity<>(listData, HttpStatus.OK);
    }

    public EmployeeData toEmployeeData(EmployeeEntity employeeEntity){ // d.
        EmployeeData employeeData = new EmployeeData();
        employeeData.setEmployeeId(Integer.valueOf(String.valueOf(employeeEntity.getEmployeeId())));
        employeeData.setFirstName(employeeEntity.getFirstName());
        employeeData.setLastName(employeeEntity.getLastName());

        return employeeData;
    }

    public EmployeeEntity toEmployeeEntity(EmployeeData employeeData){ // e.
        EmployeeEntity employeeEntity = new EmployeeEntity();
        employeeEntity.setEmployeeId(Long.valueOf(employeeData.getEmployeeId()));
        employeeEntity.setFirstName(employeeData.getFirstName());
        employeeEntity.setLastName(employeeData.getLastName());

        return employeeEntity;
    }
}

Notes:

// EmployeeController.kt

class EmployeeController(private val employeeService: EmployeeService): EmployeeApi {
    // POST Employee
    override fun createEmployee(employeeData: EmployeeData?): ResponseEntity<HttpCreatedResponse> {
        employeeService.postEmployee(toEmployeeEntity(employeeData))
        return ResponseEntity<HttpCreatedResponse>(HttpStatus.CREATED)
    }

    override fun getEmployeeById(employeeId: String): ResponseEntity<MutableList<EmployeeData>> { // a.
        val list: List<EmployeeEntity> = employeeService.getEmployeeById(employeeId.toLong())
        val listData = list.mapNotNull{ data: EmployeeEntity? ->
            data?.let { toEmployeeData(it) } // b.
        }.toMutableList()
        logger.info { "===========> ${listData}"}

        return ResponseEntity<MutableList<EmployeeData>>(listData, HttpStatus.OK)
    }

    fun toEmployeeEntity(employeeData: EmployeeData?): EmployeeEntity {
        val employeeEntity = EmployeeEntity( // c.
            // Safe call operator chain
            employeeId = employeeData?.employeeId?.toLong(), // d.
            firstname = employeeData?.firstName?.toString(),
            lastName = employeeData?.lastName?.toString()
        )

        return employeeEntity
    }

    fun toEmployeeData(employeeEntity: EmployeeEntity): EmployeeData {
        val employeeData = EmployeeData() 
        employeeData.employeeId = employeeEntity.employeeId?.toInt()
        employeeData.firstName = employeeEntity.firstname
        employeeData.lastName = employeeEntity.lastName

        return employeeData
    }
}

Notes:

Logging in Kotlin

Logging in Java is quite easy as you only need to use the @Slf4j annotation that imports the framework of the same name. In Kotlin however, we can use an alternative, open-source logging framework called Kotlin Logging.

  1. Add this dependency in your POM file:
<dependency>
    <groupId>io.github.oshai</groupId>
    <artifactId>kotlin-logging-jvm</artifactId>
    <version>5.1.0</version>
</dependency>
  1. Import it wherever you want to use it:
import io.github.oshai.kotlinlogging.KotlinLogging
  1. Declare and instantiate a member variable:
private val logger = KotlinLogging.logger{}
  1. Then use it for logging using $ for string templates within string literals:
logger.info { "===========> ${listData}"}

Key Takeaway

Kotlin is quite elegant as you can see. But I will leave you with one of my mentor’s nuggets, “I can see that you are familiar with Java, but to be proficient using Kotlin you will need to change your mental configuration from declarative to functional.”

For feedback on this article, kindly text me on LinkedIn

Cheers!!

Project Code