In Kotlin, There Are No Primitives


Coming from Java, the first surprising thing about Kotlin’s type system is what’s missing: there are no primitive types. No lowercase int, no double, no boolean. In Kotlin, 42 is an Int, and Int is a class with methods you can call.

That sounds expensive — surely an object per number is slower than a raw int? It isn’t, because the compiler maps these types to JVM primitives wherever it can. You write a uniform, object-like model; you get primitive performance underneath. This post walks through the types you’ll use on day one.

val and var

Two ways to declare something. val is read-only, var is mutable:

val name = "Ada"   // can't be reassigned
var age = 36       // can be reassigned
age = 37

You rarely write the type, because Kotlin infers it. Add it when you want to be explicit or when there’s no initializer:

val count: Int = 5

The habit to build: reach for val by default, and switch to var only when you genuinely need to reassign.

The number types

The familiar set, all as proper types: Int, Long, Short, Byte for whole numbers, Double and Float for decimals. Literals pick a type by their shape and suffix:

val i = 42      // Int
val l = 42L     // Long
val d = 3.14    // Double
val f = 3.14f   // Float

Long literals can use underscores for readability:

val population = 8_100_000_000L

No implicit conversions

Here’s a sharp edge worth knowing early. Java silently widens an int to a long. Kotlin refuses:

val i: Int = 42
// val l: Long = i        // does NOT compile
val l: Long = i.toLong()  // you convert explicitly

Every numeric type has toInt(), toLong(), toDouble(), and friends. It’s more typing, but it removes a category of silent precision and overflow bugs. (Arithmetic still promotes as you’d expect — 1L + 1 is a Long.)

Boolean and Char

val ready: Boolean = true
val grade: Char = 'A'

Char is its own type, not a number. In Java you can use 'A' as the int 65 directly; in Kotlin you ask for it: grade.code returns 65. Stronger typing, fewer accidental arithmetic-on-characters surprises.

Strings and templates

Strings are immutable. The feature you’ll use constantly is the string template$ drops a value straight into the text:

val name = "Ada"
println("Hello, $name")            // Hello, Ada
println("Next year: ${age + 1}")   // ${ } for a full expression

Triple-quoted raw strings span multiple lines and ignore backslash escapes — handy for paths, JSON, and regex:

val path = """C:\Users\ada"""
val json = """
    { "name": "Ada" }
"""

null is part of the type

This is the one that changes how you write code. In Kotlin, String and String? are different types. A plain String can never hold null:

var name: String = "Ada"
// name = null           // does NOT compile

var maybe: String? = "Ada"
maybe = null             // fine — the ? permits null

Once a value can be null, the compiler makes you handle it. The safe-call ?. returns null instead of throwing:

val length: Int? = maybe?.length

The Elvis operator ?: supplies a fallback when the left side is null:

val length: Int = maybe?.length ?: 0

And !! asserts a value isn’t null, throwing if you’re wrong — the escape hatch you should rarely reach for:

val length: Int = maybe!!.length

This single design choice removes most NullPointerExceptions from everyday Kotlin. After a null check, the compiler even narrows String? to String for you — that’s smart casting, a topic of its own.

Any, Unit, and Nothing

Three special types worth meeting early:

  • Any is the root of the hierarchy — the supertype of every non-null type, Kotlin’s equivalent of Object. (Any? is the type that also includes null.)
  • Unit is the type of “no meaningful value,” returned by functions that would be void in Java. Unlike void, it’s a real type with a single value — which matters once you start passing functions around.
  • Nothing is the bottom type: the type of an expression that never returns, such as one that always throws. A function returning Nothing tells the compiler that control stops there.
fun fail(message: String): Nothing = throw IllegalStateException(message)

val name = maybe ?: fail("name required")
// the compiler now knows `name` is a non-null String

Final thoughts

Kotlin trades a little up-front explicitness — no hidden numeric conversions, no char-as-int, conversions you have to spell out — for far fewer surprises at runtime. The headline win is null: by making it part of the type, Kotlin turns a whole class of NullPointerException into a compile error you fix before it ships.

This is the first post in a series working through Kotlin from the ground up. Next: when, the single construct that replaces switch, long if/else chains, and instanceof all at once.