Question

How to Check Type at Runtime in Kotlin: instanceof, is, and Smart Casts

kotlinkotlin-extension

Question

In Kotlin, I have a method that accepts a parameter typed as a general object, and I may pass instances of different classes when calling it.

In Java, I can use instanceof to check what class an object belongs to at runtime.

How can I do the same kind of runtime type check in Kotlin? What is the Kotlin equivalent of instanceof?

Short Answer

By the end of this page, you will understand how Kotlin checks an object's type at runtime using is and !is, how smart casts work after a successful type check, and when to use when for handling multiple possible types. You will also see common mistakes, practical examples, and how this pattern is used in real Kotlin code.

Concept

Kotlin does not use the Java keyword instanceof. Instead, Kotlin uses the is operator for runtime type checks.

For example:

if (value is String) {
    println("This is a String")
}

This checks whether value is an instance of String at runtime.

Kotlin also provides !is, which means “is not of this type”:

if (value !is String) {
    println("This is not a String")
}

Why this matters

In real programs, you often receive values whose exact type is not known in advance:

  • function parameters typed as Any
  • API responses mapped into different models
  • sealed class hierarchies
  • event systems
  • error objects

Runtime type checking lets you safely decide what to do based on the actual object type.

A key Kotlin feature: smart casts

One major improvement over Java is that after checking with is, Kotlin can automatically treat the variable as that type inside the safe branch. This is called a smart cast.

fun printLength(value: Any) {
    if (value is String) {
        println(value.length)
    }
}

Inside the if block, value is automatically treated as a String, so you can access length directly without manually casting.

Kotlin's root object type

If you want a parameter that can accept any non-null object, use Any:

fun process(value: Any) {
    // value can be many types
}

If the value can also be null, use Any?:

fun process(value: Any?) {
    // value may be null
}

This is important because Kotlin does not use Java's Object in normal Kotlin code. The idiomatic Kotlin equivalent is Any.

Mental Model

Think of is as checking a label on a package before opening it.

  • Any means “this is some package, but we do not yet know what is inside.”
  • is String means “check whether the package is labeled String.”
  • If the label matches, Kotlin lets you open it as that type automatically.

So instead of saying, “I know this is probably a String, let me cast it,” Kotlin says, “First verify the label, then I will safely treat it as a String for you.”

That automatic trust after the check is the smart cast.

Take Quiz

Syntax and Examples

Basic syntax

if (value is String) {
    println("value is a String")
}

if (value !is String) {
    println("value is not a String")
}

Example with Any

fun describe(value: Any) {
    if (value is Int) {
        println("Integer: $value")
    } else if (value is String) {
        println("String with length ${value.length}")
    } else {
        println("Unknown type")
    }
}

Explanation

  • value is declared as Any, so it can hold values of many types.
  • value is Int checks whether the runtime object is an Int.
  • value is String checks whether it is a String.
  • Inside the String branch, Kotlin smart casts value to String, so value.length works.

Using when for multiple types

fun describe(value: Any) {
    when (value) {
        is Int -> println("Integer: $value")
        is String -> println("String length: ${value.length}")
        is Double -> println("Double: $value")
        else -> println("Unknown type")
    }
}

This is often cleaner than many if/else if checks.

Example with custom classes

open class Animal
class Dog(val name: String) : Animal()
class Cat(val age: Int) : Animal()

fun handleAnimal(animal: Animal) {
    when (animal) {
        is Dog -> println("Dog name: ${animal.name}")
        is Cat -> println("Cat age: ${animal.age}")
    }
}

Here, Kotlin checks the actual subclass at runtime and smart casts accordingly.

Step by Step Execution

Consider this example:

fun printInfo(value: Any) {
    if (value is String) {
        println("Text: ${value.uppercase()}")
    } else {
        println("Not a string")
    }
}

fun main() {
    printInfo("kotlin")
    printInfo(42)
}

Step by step

First call: printInfo("kotlin")

  1. The argument is the string "kotlin".
  2. Inside printInfo, value has declared type Any.
  3. Kotlin evaluates value is String.
  4. The check is true.
  5. Kotlin smart casts value to String inside the if block.
  6. value.uppercase() runs and produces "KOTLIN".
  7. Output:
Text: KOTLIN

Second call: printInfo(42)

  1. The argument is the integer 42.
  2. value again has declared type Any.
  3. Kotlin evaluates value is String.
  4. The check is false.
  5. The else block runs.
  6. Output:
Not a string

Final output

Text: KOTLIN
Not a string

Real World Use Cases

1. Handling mixed input values

A function may receive values from different sources:

fun logValue(value: Any) {
    when (value) {
        is String -> println("Message: $value")
        is Int -> println("Count: $value")
        is Boolean -> println("Enabled: $value")
    }
}

2. Working with API or parsing results

After parsing data, the result may be one of several model types:

when (result) {
    is Success -> println(result.data)
    is Error -> println(result.message)
}

3. UI event handling

Applications often process different event objects:

when (event) {
    is ClickEvent -> handleClick(event)
    is ScrollEvent -> handleScroll(event)
}

4. Error handling

You may inspect exception types:

catch (e: Exception) {
    when (e) {
        is IllegalArgumentException -> println("Bad input")
        is IllegalStateException -> println("Bad state")
    }
}

5. Domain models with inheritance

If multiple classes share a parent class or interface, runtime checking helps select the correct behavior.

Take Quiz

Real Codebase Usage

In real Kotlin projects, developers often use type checks in a few common patterns.

Guard clauses

Exit early if a value is not the expected type:

fun process(value: Any) {
    if (value !is String) return
    println(value.length)
}

This keeps the rest of the function clean.

when for branching by type

fun render(item: Any) {
    when (item) {
        is String -> println(item)
        is Int -> println(item * 2)
        else -> println("Unsupported")
    }
}

Validation before using data

fun save(input: Any) {
    require(input is String) { "Input must be a String" }
    println(input.length)
}

Working with interfaces and polymorphism

interface PaymentMethod
class Card(val number: String) : PaymentMethod
class Cash(val amount: Double) : PaymentMethod

fun handlePayment(method: PaymentMethod) {
    when (method) {
        is Card -> println(method.number)
        is Cash -> println(method.amount)
    }
}

Prefer design over excessive type checks

In many codebases, if you are checking types very often, it may be a sign that polymorphism, interfaces, or sealed classes would make the code cleaner. Type checks are useful, but they should not replace good object-oriented design.

Common Mistakes

1. Using instanceof in Kotlin

This is Java syntax, not Kotlin syntax.

Wrong

if (value instanceof String) {
    println(value)
}

Correct

if (value is String) {
    println(value)
}

2. Using Object instead of Any

In Kotlin, the usual root type is Any, not Java's Object.

Better

fun process(value: Any) {
    println(value)
}

3. Forgetting about nullability

Any cannot hold null. Use Any? if null is possible.

fun process(value: Any?) {
    if (value is String) {
        println(value.length)
    }
}

This works because null is String is false.

4. Casting without checking

Risky

val text = value as String
println(text.length)

If value is not a String, this throws an exception.

Safer

if (value is String) {
    println(value.length)
}

Or use a safe cast:

val text = value as? String
println(text?.length)

5. Expecting smart casts in every situation

Smart casts may not work if Kotlin cannot guarantee the variable stays unchanged.

Example

var value: Any = "hello"
if (value is String) {
    println(value.length)
}

This often works in simple cases, but with mutable properties or more complex contexts, Kotlin may refuse the smart cast because the value could change.

6. Comparing exact class when you really want inheritance behavior

is checks whether an object is of a type or a subtype. That is usually what you want.

If you only want the exact runtime class, that is a different check and is less common in idiomatic Kotlin.

Comparisons

is vs Java instanceof

FeatureKotlinJava
Type check operatorisinstanceof
Negative check!is!(x instanceof Type)
Automatic cast after checkYes, smart castNo, usually manual cast needed

is vs as

OperatorPurposeSafe?Result
isCheck typeYestrue or false
asForce castNoCasted value or exception
as?Safe castYesCasted value or null

Example

if (value is String) {
    println(value.length)
}

val text1 = value as String    // may crash
val text2 = value as? String   // returns null if not String

if vs when for type checks

OptionBest for
ifOne simple type check
whenMultiple possible types

Any vs Any?

TypeCan be null?Use when
AnyNoYou expect a non-null object
Any?YesThe value may be null
Take Quiz

Cheat Sheet

Runtime type checking in Kotlin

if (value is String) { }
if (value !is String) { }

Smart cast

if (value is String) {
    println(value.length)
}

After a successful is check, Kotlin often smart casts the value.

Multiple types with when

when (value) {
    is String -> println(value.length)
    is Int -> println(value)
    else -> println("Unknown")
}

Safe cast

val text = value as? String
println(text?.length)

Force cast

val text = value as String

Use carefully: this throws an exception if the type is wrong.

Root object types

Any   // non-null any object
Any?  // nullable any object

Rules to remember

  • Kotlin uses is, not instanceof
  • Kotlin uses Any, not Object, in normal Kotlin code
  • is includes subclasses too
  • null is SomeType is false
  • Prefer when when checking several types

FAQ

What is the Kotlin equivalent of instanceof?

Use the is operator.

if (value is String) {
    println("It is a String")
}

How do I check that an object is not a type in Kotlin?

Use !is.

if (value !is String) {
    println("It is not a String")
}

What type should I use instead of Java Object in Kotlin?

Use Any for non-null values and Any? if null is allowed.

Does is also match subclasses?

Yes. If an object is an instance of a subclass, is returns true for the parent type or implemented interface.

Do I need to cast after using is?

Usually no. Kotlin often smart casts automatically inside the checked branch.

What is the difference between is and as in Kotlin?

  • is checks type
  • as casts forcefully
  • as? casts safely and returns null on failure

Can I use when instead of many if checks?

Yes. when is often the cleanest way to handle multiple possible runtime types.

Take Quiz

Mini Project

Description

Create a small Kotlin function that receives values of different runtime types and prints a useful message for each one. This demonstrates how to use is, when, and smart casts in a practical way, similar to handling mixed API results, events, or user input.

Goal

Build a type inspector that accepts a value of type Any? and handles null, strings, integers, and booleans safely.

Requirements

Requirement 1 Requirement 2 Requirement 3

Take Quiz

Keep learning

Related questions

View more