Basic Types and Variables
|
This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Kotlin is statically typed like Java, but leans heavily on type inference so explicit type annotations are the exception rather than the rule.
val and var
Every declaration is either read-only (val, assignable exactly once) or mutable (var):
val name = "Ada" // read-only; "name" cannot be reassigned
var count = 0 // mutable
count += 1
val pi: Double = 3.14159 // explicit type annotation (rarely needed -- inferred here anyway)
val is not the same guarantee as Java’s final on a mutable object — a val reference cannot be
reassigned, but if it refers to a mutable object (e.g. a MutableList), that object’s contents can still
change. Prefer val by default; reach for var only when reassignment is genuinely needed. This preference
runs through the whole language — see Data Classes
and Destructuring and Collections and Sequences for
where it matters most.
Type Inference
The compiler infers a declaration’s type from its initializer, so most val/var declarations carry no
explicit type at all — val x: Int = 5 and val x = 5 compile to the identical type. An explicit type is
still required for a function parameter, and useful whenever the inferred type would be wider or narrower than
intended.
The Built-In Number, Boolean, and Char Types
| Type | Notes |
|---|---|
|
8/16/32/64-bit signed integers; integer literals default to |
|
32/64-bit IEEE 754 floating-point; literals default to |
|
|
|
a single UTF-16 code unit, in single quotes ( |
Unlike Java, Kotlin has no separate primitive vs. boxed-wrapper types at the language level — Int is
always just Int. The compiler represents it as the JVM primitive int wherever possible and only boxes to
java.lang.Integer when a nullable type (Int?) or a generic type parameter forces it — an optimization detail
the source code never has to spell out.
val age: Int = 30 // compiles to a JVM primitive int
val maybeAge: Int? = null // boxed to java.lang.Integer, because null needs a reference type
Any, Unit, and Nothing
Three types sit at the edges of Kotlin’s type hierarchy and have no direct Java equivalent:
-
Any— the root of the non-nullable type hierarchy (Kotlin’s analogue ofObject, but every type, including the number types, is a subtype of it — there is no primitive/reference split to work around). -
Unit— the return type of a function that "returns nothing meaningful," Kotlin’s analogue ofvoid, but it is a real, singleton-valued type — this is what allows a lambda parameter typed() → Unitto be used uniformly wherever a callback with no useful return value is needed. -
Nothing— the type of an expression that never completes normally (always throws, or is an infinite loop); a function likefun fail(msg: String): Nothing = throw IllegalStateException(msg)lets the compiler treat every branch after a call to it as unreachable, which is what makes?: throw …type-check cleanly in Null Safety's Elvis-operator examples.
Arrays
Array<T> is a fixed-size, mutable, indexable container, distinct from Kotlin’s List/MutableList (covered
in Collections and Sequences):
val letters = arrayOf("a", "b", "c")
val zeros = IntArray(5) // primitive-backed, no boxing: [0, 0, 0, 0, 0]
val squares = IntArray(5) { it * it } // lambda-initialized: [0, 1, 4, 9, 16]
println(letters[1]) // "b"
letters[1] = "B"
Dedicated primitive-array types (IntArray, DoubleArray, BooleanArray, …) avoid boxing overhead, mirroring
Java’s int[]/double[]/etc.; Array<Int> (boxed) also exists but is rarely what you want for numeric data.
Unsigned Integer Types
Kotlin adds unsigned variants with no Java equivalent — UByte, UShort, UInt, ULong — for values that
are conceptually never negative (sizes, bit flags, hash codes read from an external format):
val port: UInt = 8080u
val fileSize: ULong = 4_294_967_296uL
They are represented, like the signed types, as their same-width JVM primitive at runtime, with the sign reinterpreted — there is no wider "real" unsigned JVM type underneath.
See Also
-
Null Safety — how nullability forces boxing, and the
Nothingtype in practice. -
Collections and Sequences —
List/Map/Setversus arrays. -
Operators and Ranges — arithmetic operators over these types.