This article was translated from Japanese by Claude Code.
I’ve been studying Kotlin for about 2 months, and I’m currently gradually converting the project’s source code to Kotlin. As of November 7, our Kotlin adoption is nearly 40%.
This article roughly reflects on failures and issues I encountered early in my Kotlin conversion, and proposes that when converting Java source code to Kotlin, the following 2 practices are good:
Always doubt automatic conversion code
Review decompiled bytecode
Additionally, the content is excerpted from what I presented at Bonfire #2 hosted by Yahoo JAPAN!. (Presentation materials are at the bottom of this article)
Issues I Faced Early in Kotlin Conversion#
1. Trusting Automatic Conversion Too Much#
When developing Android applications using Android Studio and converting Java source code to Kotlin, an automatic conversion tool is available.
On Mac, you can use the keyboard shortcut:
⌘ Option Shift KThis allows the IDE to convert .java files to .kt files. Wonderful!
Kotlin is a null-safe language. In Kotlin development, you must explicitly specify whether a value can be null or not.
The automatic conversion will certainly produce some working code, but manual fixes are absolutely necessary. Just because automatically converted code works without issues doesn’t guarantee it’s idiomatic Kotlin or performant. The point isn’t “never use auto-converted code and always write clean code yourself!” Rather, it’s that even though auto-generated code works, it shouldn’t be blindly trusted. Instead, review how the code was converted and, if you’re new to Kotlin, explore whether there are better ways to write it.
So, always doubt automatic conversion. Manual fixes are absolutely necessary. Even seemingly problem-free code is worth checking thoroughly.
For example, let’s say you’re converting an Activity class that overrides onActivityResult from Java to Kotlin. What if the converted Kotlin source code for onActivityResult looks like this? It seems fine at first glance, but is it?
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
super.onActivityResult(requestCode, resultCode, data)
...
}The third argument data of onActivityResult can be null. In Kotlin, you need to explicitly indicate this with the ? mark to show it’s nullable. If null is dynamically assigned to a variable without the ? mark, a NullPointerException occurs and the app crashes.
So you need to write it like this:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
...
}Because automatic conversion is convenient and I was somewhat trusting it too much, I encountered this problem (゚д゚)ハッ! Again, always doubt automatic conversion. Manual fixes are absolutely necessary.
2. Not Reviewing Decompiled Bytecode#
When defining static constants in Java and wanting to do the same in Kotlin, you can use companion object:
companion object {
val SOME_VALUE = 1
}
println("$SOME_VALUE")This code seems fine at first glance, but when you decompile the bytecode generated by this Kotlin source code:
public static final int SOME_VALUE = 1;
public static final class Companion {
public final int getSOME_VALUE() {
return MainActivity.SOME_VALUE;
}
}
…
String var2 = "" + Companion.getSOME_VALUE();
System.out.println(var2);A getter is defined for the constant with a weird method name, and code you would never write in Java is generated.
In Kotlin, setter/getter are generated by default if possible for properties.
For example, if you define a property var someValue = 1 in a class, Kotlin generates setSomeValue() and getSomeValue() methods.
To keep it brief, var is used to declare mutable properties, so a setter is defined. Conversely, val is used for immutable property declarations, and properties defined with val have getters generated by default.
Looking back at the code, since we only want to define a constant, we don’t want Kotlin to generate new getters. In such cases:
For String or primitive type properties: Use
const valdefinitionFor other properties: Attach
@JvmFieldannotation
This resolves the issue.
1. For String or Primitive Type Properties: const val Definition#
The const keyword can only be applied to String or primitive type variables defined at the top level or as members within an object. Properties defined with const are defined as compile-time constants, omitting getter generation.
companion object {
const val SOME_VALUE = 1
}
println("$SOME_VALUE")When you decompile the bytecode generated by this code:
public static final int SOME_VALUE = 1;
public static final class Companion {
…
}
String var2 = "1";
System.out.println(var2);Unlike the previous bytecode, the getter has disappeared. (With the getter gone, the code is somewhat more efficient)
2. Attaching @JvmField Annotation#
The @JvmField annotation can be attached to properties where const can’t be used, informing Kotlin not to generate getters and to expose the field as a Java field.
By decompiling Kotlin bytecode, you can check whether your Kotlin source code could be further optimized, whether unnecessary methods are being generated, etc.
Summary#
You often see tweets saying “Kotlin is cute 😍”, but a single character ? indicating nullability makes a big difference. Scary, right? I’m not arguing against Kotlin by saying Java is better! I too think Kotlin is cute 😍!
However, especially when converting Java source code to Kotlin, it may be necessary to check whether the converted Kotlin code has any defects or issues. (In my case, I make a point of reviewing decompiled bytecode.)
To avoid becoming blinded by Kotlin’s charm and getting bitten by it, especially for those now converting Java source code to Kotlin, I recommend these two points:
Always check the converted code when doing automatic conversion
If possible, decompile and review the bytecode
Finally, here are the presentation materials from Yahoo! JAPAN Bonfire #2. I introduce several other things I got stuck on beyond what’s covered above:
Bonus: I’ll be presenting about Flux at DroidKaigi in February next year. Since it’s my first time presenting for a long 30 minutes in English, I’m nervous, but since I was selected, I’ll do my best!
That’s all!