Question
Kotlin List Casting: Safely Converting List<*> to List<Waypoint> Without Unchecked Cast Warnings
Question
I want to write a Kotlin function that returns all items in a list except the first and last items, treating those middle items as via points. The function receives a generic List<*> as input, and it should only return a result if every element in the list is of type Waypoint.
Here is the code:
fun getViaPoints(list: List<*>): List<Waypoint>? {
list.forEach { if (it !is Waypoint) return null }
val waypointList = list as? List<Waypoint> ?: return null
return waypointList.filter {
waypointList.indexOf(it) != 0 && waypointList.indexOf(it) != waypointList.lastIndex
}
}
When casting List<*> to List<Waypoint>, Kotlin shows this warning:
Unchecked cast: kotlin.collections.List<*> to kotlin.collections.List<Waypoint>
What is the correct way to implement this function without getting that warning?
Short Answer
By the end of this page, you will understand why Kotlin warns about unchecked casts with generic collections, how type erasure affects List<*>, and how to safely convert or validate a list of unknown elements into a List<Waypoint>. You will also learn a cleaner way to return all middle elements without using inefficient index lookups.
Concept
Kotlin allows you to check the runtime type of each element in a collection, but not the full generic type of the collection itself.
For example, Kotlin can tell whether a value is a Waypoint:
if (item is Waypoint) { ... }
But at runtime, Kotlin cannot fully verify whether a list is really a List<Waypoint> because generic type information is erased. This is called type erasure.
That is why this cast produces a warning:
list as? List<Waypoint>
Kotlin is saying:
- "I can cast this to
List," - "but I cannot prove at runtime that its elements are really
Waypointvalues."
Why this matters
If you ignore unchecked cast warnings, your code may compile but fail later when you read values as the wrong type.
The safe way is usually one of these:
- validate every element before treating the list as typed
- build a new typed list using
filterIsInstance<T>() - avoid the cast entirely by designing the function to accept
List<Waypoint>if possible
In your specific case, you do not need to cast the original list at all. Once you know every element is a , you can create a proper typed list safely.
Mental Model
Think of List<*> as a box of items with the label "some kind of list".
You can open the box and inspect each item one by one:
- this item is a
Waypoint - this item is a
String - this item is
null
But the box itself does not carry a reliable runtime label saying "this is definitely a List".
So Kotlin lets you inspect the contents, but it warns you when you try to relabel the entire box based only on a generic cast.
A safer approach is:
- inspect the contents
- create a new box containing only the validated
Waypointitems - work with that new typed list
Syntax and Examples
In Kotlin, these are the most common approaches.
1. Validate all elements, then build a typed list
data class Waypoint(val name: String)
fun getViaPoints(list: List<*>): List<Waypoint>? {
if (!list.all { it is Waypoint }) return null
val waypoints = list.map { it as Waypoint }
return waypoints.drop(1).dropLast(1)
}
Why this works
list.all { it is Waypoint }checks every element first- after that,
map { it as Waypoint }is logically safe drop(1).dropLast(1)removes the first and last elements cleanly
This approach avoids casting the entire list to List<Waypoint>.
2. Use filterIsInstance when partial matches are acceptable
val waypoints = list.filterIsInstance<Waypoint>()
Step by Step Execution
Consider this example:
data class Waypoint(val name: String)
fun getViaPoints(list: List<*>): List<Waypoint>? {
if (!list.all { it is Waypoint }) return null
val waypoints = list.map { it as Waypoint }
return waypoints.drop(1).dropLast(1)
}
Now run it with:
val input = listOf(
Waypoint("Start"),
Waypoint("Via 1"),
Waypoint("Via 2"),
Waypoint("End")
)
Step 1: Check all elements
list.all { it is Waypoint }
Kotlin checks each element:
Waypoint("Start")-> trueWaypoint("Via 1")-> trueWaypoint("Via 2")-> true
Real World Use Cases
This pattern appears often when working with dynamic or loosely typed data.
1. Parsing external API data
An API may return mixed or uncertain values. Before treating a list as a typed model list, you validate all elements.
2. Processing data from generic libraries
Some frameworks return List<*>, Any, or deserialized objects. You often need to verify the contents before using them.
3. Route planning and map applications
Your exact example fits here:
- first item = start point
- last item = destination
- middle items = via points
4. Reading from configuration or JSON
If raw data is converted into generic structures first, you may need to confirm every item is the expected type.
5. Defensive programming in shared code
Library code often validates inputs carefully instead of assuming callers passed the right generic type.
Real Codebase Usage
In real projects, developers usually avoid unchecked casts where possible and prefer one of these patterns.
Guard clause for validation
A guard clause exits early when data is invalid:
if (!list.all { it is Waypoint }) return null
This keeps the rest of the function clean.
Transform after validation
After checking the elements, convert them into a typed list:
val waypoints = list.map { it as Waypoint }
This is much safer than casting the whole list.
Use collection helpers instead of manual index logic
Instead of:
waypointList.filter {
waypointList.indexOf(it) != 0 && waypointList.indexOf(it) != waypointList.lastIndex
}
developers usually write:
waypoints.drop(1).dropLast(1)
This is clearer and avoids repeated searches.
Prefer stronger function signatures
If the function should only accept waypoint lists, declare that directly:
: List<Waypoint>
Common Mistakes
1. Casting the whole list directly
Broken example:
val waypoints = list as List<Waypoint>
Why it is a problem:
- Kotlin cannot verify the generic element type at runtime
- this produces an unchecked cast warning
- it may hide bugs
Better:
if (!list.all { it is Waypoint }) return null
val waypoints = list.map { it as Waypoint }
2. Using filterIsInstance when you need full validation
Broken for this requirement:
val waypoints = list.filterIsInstance<Waypoint>()
Why it is a problem:
- it removes invalid items instead of rejecting the input
- you may get a partial result from bad data
Use it only if partial filtering is what you want.
3. Using indexOf inside filter
Broken style:
waypointList.filter {
waypointList.indexOf(it) != && waypointList.indexOf(it) != waypointList.lastIndex
}
Comparisons
| Approach | What it does | Good for this problem? | Notes |
|---|---|---|---|
list as List<Waypoint> | Casts the whole list | No | Unsafe and gives unchecked cast warning |
list as? List<Waypoint> | Safe cast syntax | No | Still unchecked for generic element type |
list.all { it is Waypoint } + map | Validates every element, then converts | Yes | Clear and safe |
list.filterIsInstance<Waypoint>() | Keeps only matching elements | Not for strict validation | Good only if partial filtering is acceptable |
| Function parameter |
Cheat Sheet
fun getViaPoints(list: List<*>): List<Waypoint>? {
if (!list.all { it is Waypoint }) return null
val waypoints = list.map { it as Waypoint }
return waypoints.drop(1).dropLast(1)
}
Key rules
List<*>means the element type is unknownList<*>cannot be safely cast toList<Waypoint>without an unchecked cast warning- Kotlin generic type arguments are erased at runtime
- Check elements individually with
is - Build a typed list with
mapafter validation - Use
drop(1).dropLast(1)to get middle elements
Useful patterns
Validate all elements:
if (!list.all { it is Waypoint }) return null
Convert after validation:
FAQ
Why does Kotlin warn about casting List<*> to List<Waypoint>?
Because Kotlin cannot fully check generic element types at runtime due to type erasure.
Is as? List<Waypoint> safer than as List<Waypoint>?
It avoids throwing if the outer type is wrong, but it still cannot verify the generic element type, so the cast remains unchecked.
Can I use filterIsInstance<Waypoint>() here?
Yes, but only if you want to keep matching items and ignore the rest. It is not correct if invalid input should make the function return null.
What is the cleanest way to get all middle elements in Kotlin?
Usually:
list.drop(1).dropLast(1)
Should this function accept List<*> or List<Waypoint>?
Use List<Waypoint> if possible. Use List<*> only when the input truly comes from an unknown or generic source.
What happens if the list has fewer than three elements?
There are no middle elements, so returns an empty list.
Mini Project
Description
Build a small route utility for a mapping app. The input may come from generic data, so your code must validate that all items are Waypoint objects before processing them. This demonstrates safe handling of List<*>, avoiding unchecked casts, and extracting only the middle route points.
Goal
Create a function that accepts a generic list, returns only the middle Waypoint items, and returns null if any element is not a Waypoint.
Requirements
- Define a
Waypointdata class. - Write a function that accepts
List<*>. - Return
nullif any item is not aWaypoint. - Return all items except the first and last when the input is valid.
- Demonstrate the function with both valid and invalid input.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.