skip to content

Casts: as / as?

as throws ClassCastException on mismatch, as? yields null instead. The follow-up is generics: under erasure a cast to List<String> cannot be checked at runtime, which is exactly what the unchecked-cast warning is telling you.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is the difference between the as and as? cast operators in Kotlin, and what happens when each fails?

level: juniorimportance: must knowfreq 75%

answer

  1. as = throw CCE on mismatch
  2. as? = null on mismatch, result is nullable
  3. null as NonNull throws; null as? yields null
  4. as? + Elvis ?: is the idiom
  5. prefer is smart-cast / as? over bare as

basics

~20 s

as forces a value to a type and crashes with an error if it is not that type. as? tries the same but gives back null instead of crashing when the value does not match.

solid answer

~40 s

as is the unsafe cast operator: x as Foo asserts the runtime type and throws ClassCastException if x is not a Foo. as? is the safe cast operator: x as? Foo returns the value typed as Foo when it matches and null otherwise, so its result type is always nullable (Foo?). Casting null with as to a non-null type also throws; null as? Foo just yields null. Because as? never throws, it pairs naturally with the Elvis operator, e.g. val f = x as? Foo ?: return. Prefer as? plus null handling, or a smart-cast via is, over a bare as that can blow up at runtime.

code

kotlin · 9 lines
kotlin
val obj: Any = listOf(1, 2, 3)

// unsafe: throws ClassCastException
// val s = obj as String

// safe: yields null, no crash
val s: String? = obj as? String        // null
val list = obj as? List<*> ?: emptyList<Any?>()
println(list.size)                     // 3

go deeper

for a junior

States that as throws ClassCastException and as? returns null on mismatch.

for a middle

Knows as? result type is nullable and pairs it with Elvis; handles the null-receiver cases.

for a senior

Prefers is smart-casts/as? over bare as, explains when a loud CCE is actually the right design choice.

for a principal

Frames cast choice as an error-handling policy decision and guides API contracts so casts are rarely needed.

## The two cast operators Kotlin has two infix cast operators that convert a reference from one static type to another. - **`as` (unsafe cast)**: `value as TargetType` asserts that `value` really is a `TargetType` at runtime. If it is, the expression has static type `TargetType`. If it is **not**, the JVM throws a `ClassCastException` (`CCE`). - **`as?` (safe cast)**: `value as? TargetType` does the same check but, on failure, evaluates to `null` instead of throwing. Its result type is therefore always **nullable** — `TargetType?`. ## Why the result of `as?` is nullable Because `as?` can legitimately produce `null` (on a type mismatch), the compiler widens the result type to `TargetType?`. This is true even if `TargetType` is itself non-null: ```kotlin val anything: Any = "hello" val s: String = anything as String // ok, smart static type String val n: String? = anything as? String // result type is String?, value "hello" val bad: String? = 42 as? String // null, no exception // val boom = 42 as String // throws ClassCastException ``` ## Null handling A `null` receiver behaves differently per operator: - `null as String` throws `ClassCastException` (null is not a `String`). - `null as String?` succeeds — the target is nullable, so null is allowed. - `null as? String` yields `null` (no throw). ## Idiomatic use with Elvis `as?` composes cleanly with the **Elvis operator** `?:` to provide a fallback or early exit: ```kotlin fun describe(x: Any): String { val text = x as? String ?: return "not a string" return "string of length ${text.length}" } ``` ## When to use which - Use **`is` smart casts** when you branch on type — they are checked and need no operator on the body. - Use **`as?`** when a mismatch is expected/handled and you want null, not a crash. - Use **`as`** only when you are certain of the type (e.g., from an external contract) and a mismatch is a programming bug you want surfaced loudly.

  • What is the static type of the expression `x as? Int`?
    `Int?` — `as?` always produces a nullable type because it returns null on a failed cast.
  • Does `null as? String` throw?
    No. It evaluates to `null`. Only the unsafe `as` to a non-null type throws on a null/mismatched value.

as is a strict bouncer who throws you out if your ID is wrong; as? just shakes their head and lets you walk away (returns null).

saying these in an interview costs you the question

  • Saying as? returns the non-null type (it always returns a nullable type)
  • Claiming as never throws / catches the error itself
  • Thinking null as? Foo throws ClassCastException
  • Confusing as (cast) with is (type check)
  • Believing as performs a conversion like toInt() rather than a runtime type assertion

context

open as a page

Why does `value as List<String>` produce an 'unchecked cast' warning in Kotlin, and what are the consequences at runtime?

level: middleimportance: must knowfreq 60%

basics

~20 s

At runtime the program does not remember the type inside the list, only that it is a list. So Kotlin can check it is a List but not that it holds Strings. It warns because the cast might wrongly succeed and crash later.

open as a page

Show idiomatic ways to use `as?` to avoid ClassCastException, and explain how it interacts with smart casts and the Elvis operator.

level: middleimportance: should knowfreq 50%

basics

~20 s

Use as? to try a cast and get null if it does not fit, then handle the null with the ?: operator to provide a default or return early. This avoids crashes from a bad cast.

open as a page

What is the runtime difference between `null as String` and `null as String?`, and how do platform types from Java affect cast safety?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Casting null to a type that forbids null fails, but casting null to a nullable type is fine. Values coming from Java have an unknown null status, so a careless cast can crash later when a hidden null appears.

open as a page

You must safely cast an erased `Any` (e.g., a deserialized value) into a generic type like `List<User>`. How do you make this robust given type erasure, and when is `@Suppress("UNCHECKED_CAST")` justified?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Because the program forgets the inside type of a list at runtime, you cannot fully trust one big cast. Check each element instead, or use a tool that keeps the type, and only silence the warning when something else guarantees the contents are correct.

open as a page