"Incompatible Types": Why Kotlin Shouts at Your List?
\n\nHave you ever encountered a situation where Kotlin, this "smart" and "safe" language, suddenly gives you a yellow warning Unchecked Cast: kotlin.collections.List<Kotlin.Any?> to kotlin.collections.List<Waypoint>? If yes — you are not alone. This question on Stack Overflow has gathered thousands of views, and it's no surprise. Anyone who switches from Java or starts working more deeply with generics in Kotlin will eventually run into this "rake".
In this article, we won't just give a dry explanation. We'll break down why this happens, show three ways to correctly solve the problem, and most importantly — how to write code that won't crash at runtime. Ready? Let's go!
\n\nThe Core Issue: Type Erasure
\n\nLet's get straight to the point. Imagine you have a function that returns a list of anything — List<Any?>. But you need a list of specific objects, for example, List<Waypoint>. The "simplest" solution that comes to mind is explicit casting:
val rawList: List<Any?> = getDataFromSomewhere() // Let's say it's JSON or a database\nval waypointList: List<Waypoint> = rawList as List<Waypoint> // Unchecked Cast! ⚠️\n\nKotlin (and Java under the hood) uses a mechanism called Type Erasure. This means that during program execution, information about the type inside generics (what's in the angle brackets) is erased. For the JVM, your List<Waypoint> is just a List. The compiler sees that you're trying to trick the system and gives a warning: "Hey, I can't verify that there are really Waypoints inside, you're taking responsibility!"
If you are 100% sure that there are only Waypoint objects inside, you can suppress the warning with the @Suppress("UNCHECKED_CAST") annotation. But if there turns out to be, say, a String — you'll get a ClassCastException at runtime. And that's exactly what Kotlin is trying to protect us from.
What NOT to Do: Common Beginner Mistakes
\n\nHere are a few examples I often see on forums. Remember them so you don't repeat them:
\n- \n
- Blind casting:
list as List<Waypoint>— the most dangerous option. \n - Ignoring the warning: "It works!" — it works until it crashes. \n
- Trying to cast in a chain:
(list as? List<Waypoint>)— the safe operatoras?won't help if the list contains the wrong objects. It only checks that it's aList, but not its contents. \n
3 Working Methods: From Simple to Professional
\n\nLet's look at real code examples. Suppose we have a class Waypoint:
data class Waypoint(val latitude: Double, val longitude: Double, val name: String)\n\nAnd there's a "dirty" list obtained from an external API:
\n\nfun fetchRawData(): List<Any?> {\n return listOf(\n mapOf("lat" to 55.75, "lon" to 37.62, "name" to "Moscow"),\n mapOf("lat" to 59.93, "lon" to 30.31, "name" to "SPb")\n )\n}\n\nMethod 1: Filtering with filterIsInstance (Recommended ✅)
\n\nThis is the most idiomatic and safe way. The filterIsInstance<T>() function will filter only those elements that are instances of the specified type and return a already typed list.
fun getWaypointsSafely(): List<Waypoint> {\n val rawData: List<Any?> = fetchRawData()\n // Let's say we somehow convert Map to Waypoint\n val waypoints: List<Waypoint> = rawData\n .filterIsInstance<Map<*, *>>()\n