Passing null to overloaded Java methods from Kotlin
Interoperability between Java and Kotlin is great, but the difference in how nullability is treated can sometimes pose interesting problems. This article tackles one particular problem and introduces some Kotlin concepts in the process.
Calling an overloaded Java method from Kotlin
Given a Java class called CakeBaker with two overloaded methods.
class CakeBaker {
static Cake bakeCake(Recipe recipe) {...}
static Cake bakeCake(String name) {...}
}
Both methods result in a cake, but the first accepts a recipe and the second simply accepts the name and produces the cake in some mystical way.
When importing this Java class into your Kotlin code and calling CakeBaker.bakeCake(..) with either a Recipe or a String, everything works as expected and life is great.
Calling an overloaded Java method from Kotlin with null as argument
Something peculiar happens when you don’t have a recipe or name to pass as an argument and you still need to call the method.
Perhaps it has some functional meaning, or perhaps you want to write a test to validate that your Kotlin application knows how to deal with the implicit nullability of the Java method.
You pass null as input into the method:
CakeBaker.bakeCake(null)
This should be valid, as Java objects are implicitly nullable, right? Wrong. The cake is a lie. The Kotlin compiler throws an error:
Overload resolution ambiguity between candidates:
fun bakeCake(recipe: Recipe!): (Mutable)Cake!
fun bakeCake(name: String!): (Mutable)Cake!
What’s going on? Because the Java objecttypes of both overloaded methods are nullable, the compiler doesn’t know which overloaded method you’re trying to call. That makes sense.
Were you to call this method from Java, you’d get a similar error:
error: reference to bakeCake is ambiguous
bakeCake(null);
^
both method bakeCake(String) in CakeBaker and method bakeCake(Recipe) in CakeBaker match
Calling a method from Kotlin using Named Arguments
You might think: "This is Kotlin, so I can use Named Arguments to tell the compiler which of the two overloaded methods to use and be done with it!"
CakeBaker.bakeCake(name = null)
Unfortunately, this will also not compile:
Named arguments are prohibited for non-Kotlin functions.
Calling a method from Kotlin with null cast to a specific type
The only remedy is to tell Kotlin explicitly which of the two nullable types you are nullifying, by Casting it.
At this point it’s important to point out that String and String? are two disctinctly different types in Kotlin.
-
Stringis never null. -
String?is aStringornull. -
An imported Java
Stringis treated[1] asString?in Kotlin code.
With that in mind: Kotlin offers two ways to typecast: as and as?.
X as? String means: Treat X as type String if you can; otherwise treat it as null.
The Kotlin compiler implicitly treats that as String?.
Let’s try that.
CakeBaker.bakeCake(null as? String)
This compiles and runs.
But we don’t want to try and treat null as String; we want to explicitly treat it as String?, the exact object type expected by the Java method.
Also, IntelliJ grays out the as? String bit, because it figures that as? after null always results in null, making the rest redundant.
When you then remove it as suggested however, you’re met with the overload ambiguity compiler error again that we were trying to solve.
So this approach is a bit of a hack.
The correct way to cast null to String? in Kotlin is by doing it explicitly, as follows.
CakeBaker.bakeCake(null as String?)
Now Kotlin will understand the null you’re passing it was intended as the nullable String?, which translates to the Java String.
IntelliJ approves of this as well.
And importantly: the intent is clear.
In Java, you’d similarly cast null to one of the arguments:
CakeBaker.bakeCake((String) null);
Calling a method from Kotlin via an extension method
The aforementioned solution is not pretty, and if you had the option you’d probably want to add a method to the Java library to avoid this situation entirely:
class CakeBaker {
static Cake bakeCake(Recipe recipe) {...}
static Cake bakeCake(String name) {...}
static Cake bakeCake() {
return bakeCake((String) null);
}
}
But you don’t always have that option when using others' external libraries.
Kotlin does offer another thing that can help avoid having to cast null every time you need to call the the method.
For convenience, you could create an Extension Function in your calling Kotlin code:
fun CakeBaker.bakeCake() = bakeCake(null as String?)
The original Java class stays untouched, yet you can now call the CakeBaker.bakeCake() method for the same effect.
Reference list
String is represented by the platform type String! which boils down to Kotlin considering it either a String or a String?, but in practice when writing Kotlin code using Java libraries, you’ll be treating Java String as Kotlin String?.