When writing code in Kotlin, you may encounter an error called “Overload resolution ambiguity”.
This error occurs when, for example, multiple functions or properties with the same name exist and the Kotlin compiler cannot determine which one to use.
Kotlin supports overloading, which allows functions with the same name to be used differently depending on factors such as the types and number of arguments, but an error occurs when the candidates cannot be narrowed down to one based on how the function is called.
This article introduces the main causes of the “Overload resolution ambiguity” error in Kotlin and the basic ways to resolve it.
- What Is “Overload resolution ambiguity”?
- Cause 1: Multiple Candidates Exist for a Function with the Same Name
- Cause 2: Passing null as an Argument
- Cause 3: The Type of a Lambda Expression Cannot Be Determined
- Cause 4: Multiple Candidates Exist for a Method Reference
- Cause 5: Multiple Functions with the Same Name Exist Due to Imports
- Cause 6: Multiple Extension Function Candidates Exist
- Cause 7: The Type of a Numeric Literal Is Ambiguous
- Resolve the Error by Explicitly Specifying the Argument Type
- Review Unnecessary Overloads
- Check for Unnecessary Imports
- Points to Check When “Overload resolution ambiguity” Appears
- Summary
What Is “Overload resolution ambiguity”?
“Overload resolution ambiguity” is an error indicating that multiple candidates exist, making it impossible to determine which function or property should be used.
In Kotlin, you can define functions with the same name but different argument types or numbers of arguments.
fun show(value: Int) {
println("Int: $value")
}
fun show(value: String) {
println("String: $value")
}
With definitions like these, show(10) selects the function that accepts an Int, while show("Hello") selects the function that accepts a String.
However, if the information available at the time of the call is not enough to determine which of multiple candidates should be used, “Overload resolution ambiguity” is displayed.
Cause 1: Multiple Candidates Exist for a Function with the Same Name
The most basic cause is that multiple functions with the same name exist and the compiler cannot determine from the arguments which function should be called.
fun printValue(value: String?) {
println(value)
}
fun printValue(value: Int?) {
println(value)
}
fun main() {
printValue(null)
}
In this example, there is a printValue() that accepts String? and another printValue() that accepts Int?.
If only null is passed, it can be passed to either nullable type, so the compiler cannot determine which one should be used.
In this case, explicitly specify the type to narrow the candidates down to one.
fun main() {
printValue(null as String?)
}
This makes it clear that the function accepting String? should be used.
Cause 2: Passing null as an Argument
When an overloaded function has multiple nullable types as arguments, passing null directly may make the call ambiguous.
fun process(value: String?) {
println("String")
}
fun process(value: List<String>?) {
println("List")
}
fun main() {
process(null)
}
Because null can be assigned to both String? and List<String>?, the compiler cannot determine which process() should be used.
You can also resolve this by assigning the value to a variable with an explicit type before passing it.
fun main() {
val value: String? = null
process(value)
}
In this case, because the type of value is known to be String?, the corresponding function is selected.
Cause 3: The Type of a Lambda Expression Cannot Be Determined
When overloaded functions accept lambda expressions as arguments, the lambda expression alone may not provide enough information to determine which function type should be used.
fun execute(action: (Int) -> Unit) {
action(1)
}
fun execute(action: (String) -> Unit) {
action("Hello")
}
When overloads that accept similar function types exist like this, depending on how they are called, the compiler may not be able to determine a single type for the lambda expression.
In that case, explicitly specify the argument type of the lambda expression.
execute { value: Int ->
println(value)
}
By explicitly specifying the lambda expression argument as Int, it becomes clear that the function accepting (Int) -> Unit should be used.
Cause 4: Multiple Candidates Exist for a Method Reference
When using function references or method references, the reference may also become ambiguous if multiple functions with the same name exist.
fun convert(value: Int): String {
return value.toString()
}
fun convert(value: Double): String {
return value.toString()
}
When multiple convert() functions exist like this, simply writing ::convert may not provide enough information to determine which one should be referenced, depending on the context in which it is used.
In that case, explicitly specify the function type that will receive the reference.
val converter: (Int) -> String = ::convert
By explicitly specifying the function type in this way, the convert() function that accepts an Int is selected.
Cause 5: Multiple Functions with the Same Name Exist Due to Imports
The call destination may also become ambiguous when functions with the same name are imported from different packages.
import packageA.calculate
import packageB.calculate
fun main() {
calculate()
}
If both packages contain a calculate() function that can be called under the same conditions, the compiler cannot determine which one should be used.
In this case, you can make the call destination clear by using a fully qualified name.
fun main() {
packageA.calculate()
}
You can also assign aliases when importing.
import packageA.calculate as calculateA
import packageB.calculate as calculateB
fun main() {
calculateA()
}
Cause 6: Multiple Extension Function Candidates Exist
Kotlin allows you to define extension functions, but if multiple available extension functions have the same name, the call destination may become ambiguous.
fun String.display() {
println(this)
}
Depending on the scope and imports, if multiple extension functions with the same name are available for the same type, the compiler may not be able to narrow the candidates down to one.
In this case, remove unnecessary imports or use import aliases to make it clear which function should be used.
Cause 7: The Type of a Numeric Literal Is Ambiguous
When multiple overloads accept numeric values, the function being called may become difficult to determine depending on the result of type inference.
fun calculate(value: Long) {
println("Long")
}
fun calculate(value: Double) {
println("Double")
}
In cases like this, explicitly specifying the type of the numeric literal or variable as necessary makes it clearer which function should be used.
fun main() {
calculate(10L)
calculate(10.0)
}
Because 10L is treated as a Long and 10.0 is treated as a Double, the corresponding functions are selected.
Resolve the Error by Explicitly Specifying the Argument Type
When “Overload resolution ambiguity” is displayed, one of the most basic solutions is to explicitly specify the type of the argument or variable.
val value: String? = null
printValue(value)
By explicitly specifying the type, the compiler can narrow down the available candidates.
In particular, null, lambda expressions, and function references may not provide enough information to determine their types on their own, so explicitly specifying the type may resolve the issue.
Review Unnecessary Overloads
If many functions with the same name are overloaded, ambiguity may be more likely to occur at the call site.
If you have created functions that accept many similar types or nullable types, it is also important to check whether they really need to use the same function name.
Rather than avoiding the error only at the call site, it can also be useful to check whether the overloads themselves have become difficult to understand in terms of design.
Check for Unnecessary Imports
Even if you have not defined functions with the same name yourself, imported functions or extension functions may cause “Overload resolution ambiguity”.
In IDEs such as Android Studio, automatic imports may sometimes add functions that you did not intend to use.
Check not only the location of the error but also the import statements at the top of the file, and make sure that multiple functions or extension functions with the same name have not been imported.
Points to Check When “Overload resolution ambiguity” Appears
When “Overload resolution ambiguity” appears, check whether multiple candidates exist from the compiler’s perspective for the function or property causing the error.
- Are there multiple overloaded functions with the same name?
- Are you passing
nulldirectly as an argument? - Is the argument type of a lambda expression ambiguous?
- Are there multiple candidates for a function reference or method reference?
- Are functions with the same name imported from different packages?
- Are multiple extension functions with the same name available?
- Can the candidates be narrowed down to one by explicitly specifying the type of an argument or variable?
In particular, if the error suddenly occurs, checking not only the code itself but also newly added imports and newly defined overloads can make it easier to identify the cause.
Summary
Kotlin’s “Overload resolution ambiguity” is an error that appears when multiple candidates exist for functions, properties, or other elements with the same name, and the compiler cannot determine a single one that should be used.
Typical causes include passing null to an overloaded function, ambiguous types for lambda expressions or function references, and importing multiple functions or extension functions with the same name.
Basically, the error can be resolved by narrowing the candidates down to one, such as by explicitly specifying the type of an argument or variable, explicitly specifying a function type, or using a fully qualified name or an import alias.
Also, if there are too many overloads or many functions with similar types, reviewing the function design itself can be effective.
When the error appears, instead of looking only at the call site, check functions with the same name, argument types, lambda expressions, function references, imports, and other related elements, and verify whether “the compiler can determine a single candidate.” This makes it easier to identify the cause.
