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:
- Maven as your build automation tool
- Kotlin as your language of choice
- Spring Boot version 3.2.3
- Java 17 or 21
- Jar as your file zipper. If we were building a web application to be deployed on a servlet or JSP container, we would have selected War instead of Jar.
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:
- We use Lombok annotations such as
@Data
to bundle up features such as@Getter
that abstracts Getters,@Setter
that abstracts Setters and other functions such asToString
that we would otherwise have written down. - Member variables of an entity class use
private
as an access modifier to ensure encapsulation of the public class, and prevent anyone outside the class from modifying the entity’s data.
// EmployeeEntity.kt class
@Entity
@Table(name="employee")
class EmployeeEntity(
@Id internal var employeeId: Long?,
internal var firstname: String?,
internal var lastName: String?
)
Notes:
- A Kotlin class by default uses
public
access modifier. Therefore, we do not need to use it in our class declaration. - Mutable/Write variables are declared with the keyword
var
. However, if we had to declare an immutable variable, we would have used the keywordval
. employeeId
,firstName
andlastName
are fields or member variables for theEmployeeEntity
class. However, Kotlin generates setters and getters, so there is no need of us using@Data
annotation like we did in Java.- The
internal
keyword is an access modifier for member variables which will only be visible within the module, the class has been declared. - While
EmployeeEntity
is a class, it is also a primary constructor within Kotlin’s classes. Since the class does not have any annotations or visibility modifiers, we do not need to explicitly use theconstructor
keyword. Otherwise, the class would be declared with the constructor keyword as follows.class EmployeeEntity constructor(..){..}
. ?
allows for null safety in cases where the member variable is initialized with a null value. Without?
, then a nullable declaration on such a variable would cause a compilation error. Note that this will not be a Null Pointer Exception that would otherwise be thrown in Java.
Employee Repository
// EmployeeRepository.java class
public interface EmployeeRepository extends CrudRepository<EmployeeEntity, Long> {
List<EmployeeEntity> findByEmployeeId(Long employeeId);
}
Notes:
- We extend the functionalities of the imported
CrudRepository
interface into theEmployeeRepository
interface. Therefore, we are able to define custom methods that will be used in the business logic of other functions when need arises.
// EmployeeRepository.kt
interface EmployeeRepository: CrudRepository<EmployeeEntity, Long> {
fun findByEmployeeId(employeeId: Long): List<EmployeeEntity>
}
Notes:
- As you may have noticed,
:
is used before type declarations for variables; before declaring the return type of functions; or to extend the functionalities of an interface or when inheriting properties of anopen
class. In Kotlin,open
does not meanpublic
. - Keyword
fun
is used for function declaration.
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:
- a. We first instantiate the imported
EmployeeRepository
class. We also make the field final since it should never change throughout its usage in this service class. - b. We utilize constructor injection to inject the member variable into the
EmployeeService
constructor. However, we could use@Autowired
for field injection but that would not be ideal for encapsulation. - c. We call the custom function
findByEmployeeId
that we had created in theEmployeeRepository
interface using the instance variable,employeeRepository
.
// 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:
- a. Since
EmployeeService
is a primary constructor, we injectEmployeeRepository
into the class. Since the member variable will be immutable, we use keyword,val
. - b. Kotlin uses inline functions. Therefore, this is an inline function that makes the code cleaner.
- c. Alternatively, we can use the long form of the function by adding the body of the function that returns a list type.
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:
- a.
EmployeeApi
is the class generated from the OpenAPI specification file when we runmvn clean install
. Here, we want the child classEmployeeController
to inherit the methods from the super class. - b.
createEmployee
method expects a parameter of typeEmployeeData
. However, we declared an entity calledEmployeeEntity
. Therefore, we call a custom method, e, that converts theEmployeeEntity
toEmployeeData
. - c.
getEmployeeById
method returns a list of typeEmployeeData
wrapped in aResponseEntity
. However, ourgetEmployeeById
function that we has been implemented in theEmployeeService
returns a list of typeEmployeeEntity
. Therefore, we call a custom function ,d, that does the necessary conversion. Note the usage of theLambda
.
// 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:
- a. Kotlin uses the keyword
override
, to customary implement methods inherited from a super class or implement methods declared in an interface within a class. In addition, Note the use ofMutableList
. The collection type declares that the list type returned by the function is elastic/writable. - b.
let
keyword is a scope function we use within the context of thedata
, context object of typeEmployeeData
from the element transformation lambda,mapNotNull
. We then use the keyword,it
, to reference the context object.toMutableList()
, is a method that creates an explicit casting of what is returned by the lambda into aMutableList
. - c. We instantiate
EmployeeEntity
, but since the class has named arguments, we also instantiate them. - d. We use
?.
, called a Safe Call Operator in Kotlin, that helps in checking nullable references by chaining it withEmployeeData
andemployeeId
.
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.
- Add this dependency in your POM file:
<dependency>
<groupId>io.github.oshai</groupId>
<artifactId>kotlin-logging-jvm</artifactId>
<version>5.1.0</version>
</dependency>
- Import it wherever you want to use it:
import io.github.oshai.kotlinlogging.KotlinLogging
- Declare and instantiate a member variable:
private val logger = KotlinLogging.logger{}
- 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!!