Introduction
In the realm of modern software development, the battle between Java and Kotlin continues to captivate programmers. We at PCG have had the opportunity to discuss the learning curve with Kotlin at a webinar, particularly for those coming from a Java-based development background. The seasoned consultants that we are, we have been documenting this journey and decided to bring forth some more tips, tricks and pitfalls, which may help out fellow developers on their journeys to transition from Java to Kotlin. In this article, we will dive into the Kotlin-versus-Java saga and unveil some insights into navigating this exciting shift.
Pitfalls
Compatibility with Jackson
Jackson is a popular JSON serialization/deserialization library used in Spring. Kotlin data classes, which are commonly used for modeling data in Kotlin, may have different default behavior with Jackson serialization compared to Java classes without the usage of the module 'jackson-module-kotlin’. For example, consider the following Kotlin data class:
By default, Kotlin Data classes generate equals, hashCode, and toString methods that consider all properties, including val properties. However, Jackson uses these generated methods during serialization, which may result in unexpected behavior. To resolve this issue, you can use Jackson’s annotations, such as @JsonIgnore or @JsonProperty, to customize the serialization behavior of Kotlin data classes:
Static Methods in Kotlin
In Kotlin, static methods and fields are defined inside a companion object, which is similar to a Java static inner class. In the example below, we may expect that the Kotlin compiler will compile the ‘isThisStatic’ method defined inside a companion object as a static method under the hood, but this is not the case. The companion object is formed as a static inner class, but the methods inside are simple instance methods and not static themselves. This may cause issues when using a mixed Java and Kotlin codebase, as method calls which are expected to be static won’t compile.
Kotlin provides the ‘@JvmStatic’ annotation to expose the methods and fields in the companion object as static methods and fields in Java. The ‘@JvmStatic’ annotation can only be applied to methods and not to properties. If you want to expose a property as a static field in Java, you need to define a getter method in the companion object. So the following will now compile:
IDE Converters
Use of IDE converters to convert Java code into Kotlin always needs double checks. The following snippet is an example from IntelliJ:
Mocking Libraries
Classes and methods in Kotlin are final by default, and anyone using Mocks during testing needs to know that Mockito used to have problems with that. Mockito was primarily built for Java testing and until recently you couldn’t use it for Kotlin fully as it would expect you to write your classes and methods as ‘open’. This has recently changed with the new feature introduced by Mockito, but for those using older versions when working with Kotlin, you would need to either use another mocking library OR skip mocking at all.
In case you want to avoid this, we recommend using Mockk. It provides a pure Kotlin-mocking DSL for writing similar tests and is well suited as it provides better support for Kotlin features along with mocking capabilities for final classes and methods.
Null Safety for Platform Types
Despite boosting in-built null safety, Kotlin still is unable to identify null-related issues when you have a mixed code base of Java and Kotlin. For example, the following code will not throw any compile time issues in Kotlin:
You would need to handle this as you would in Java:
Tips and Tricks
Smart Casting
Smart Casting in Kotlin allows you to NOT specify the type of variable before accessing the property itself. (No more sonar lint warnings when doing type conversions)
Elvis Operator is your friend
The Elvis operator (?) provides a way to write more intuitive and stream based code which is more focused on the task at hand and aids readability.
Function Types and Interfaces
In Kotlin, functions are first-class citizens, which means they can be treated like any other data type, such as integers, strings, or objects. A function type represents the signature of a function, including its parameter types and return type. This allows you to pass functions as arguments to other functions, return them from functions, and store them in variables or data structures. This functional programming approach allows for more flexible and expressive code, making it easier to work with complex logic and operations.
KTor
KTor is a library built from the ground up using Kotlin and co-routines that is very easy to utilize for building asynchronous client server applications. Getting a fully functional lightweight and flexible microservice up and running can be as simple as:
Summary
“In the grand tapestry of programming languages, Kotlin stands as a vibrant thread reshaping the fabric of modern development.” - ChatGPT
In this post, we presented the wisdom gained from using Kotlin in live production grade applications. By embracing them, developers can harness Kotlin’s advantage over Java. So what’s next?
We would suggest first time users go through the official Kotlin documentation to familiarize themselves with the language and continue pushing to learn more.
If you found this article useful, do share it with your friends. If you have more tips, tricks and gotcha’s, please let us know. We’d be happy to learn more about it. If you want to hear more from us and about Kotlin, check out the recording of our webinar “Modernize Your Java Applications with Kotlin: Best Practices and Strategies”
Thanks for reading!