The Kotlin Guide for the Busy Java Developer

Unless you lived in a cave for the last two years, you probably noticed that Kotlin is here and going to stay for a long time. Back in 2017, it doesn’t took long for it to skyrocket after Google announced Kotlin as an official language for Android.

Although being concise and easy to get started with, on-boarding developers to Kotlin might require some time but Java Developers could take a lot of shortcuts navigating their way through the official documentation or through the highly recommended “Kotlin In Action”.

While not a replacement to the sources cited above (despite being highly inspired from them), I wrote that guide for Java Developers trying to focus on differences and new idioms. Also, I tried to be concise by favoring commented snippets over long explanations.

Hopefully, that guide will help you to get started faster or, at least, be your Kotlin trigger?


Unlike Java, Kotlin doesn’t impose any restrictions on the layout and naming of source files: you can put multiple classes in the same file, and choose any directory structure you’re comfortable with.

However, it’s still a good practice to follow Java’s directory layout, while not hesitating to group multiple classes into the same file if they’re small and related.


In Kotlin, there are two keywords to declare a variable:

  • val — Immutable reference. A val variable can’t be reassigned after initialization and corresponds to a final variable in Java.
  • var — Mutable reference. The value of a such variable can be changed.
val i = 42  // final variable
var j = 42 // mutable variable
Semicolons in Kotlin are optional.

Kotlin’s variable declaration starts with one of the two keywords above, followed by the variable name. The variable type may comes after the name, separated by a colon. Just like Java, Kotlin is a statically typed language, but the compiler is able to determine the type from the context, this is called type inference:

val hello: String = "Hello"  // explicit type
val hello = "Hello" // inferred type
val i = 42   // Int
val l = 42L // Long
val d = 42.0 // Double
val f = 42f // Float
Kotlin supports top-level variables, you don’t need to declare them in a class.


Unlike Java, Kotlin’s == operator compare two objects by calling equals under the hood. For reference comparison, you can use the === operator instead:

val a = Pair("key", 42)
val b = Pair("key", 42)
println(a == b)  // true
println(a === b) // false
For values which are represented as primitive types at runtime, the === check is equivalent to the == check.

Null Safety

The first and most important difference between Kotlin and Java is Kotlin’s explicit support for nullable types. Putting a question mark after the type name explicitly allows the variable to contains null:

var nickname: String? = null
var name: String = "John"
name = null
^ ERROR: null can not be a value of a non-null type String.
nickname = name
name = nickname
^ ERROR: Type mismatch.

A type without a question mark denotes that variables of such type can’t store null references.

Thankfully, Kotlin provides useful tools to deal with nullable types, and the safe call operator will soon become your best friend:

// returns null if nickname is null
val uppercase = nickname?.toUpperCase()

The safe call operator above pairs extremely well with the Elvis operator:

// return "unknown" if nickname is null
val safe = nickname ?: "unknown"
// return 0 if nickname is null
val length = nickname?.length ?: 0

If you’re missing NPE, you can still throw an exception if the value is null using the not-null assertion operator:

// throw an NPE if nickname is null
val length = nickname!!.length

String Templates

Kotlin allows you to refer to local variables in string literals:

val name = "Kotlin"
val hello = "Hello $name" // "Hello Kotlin"

The literal expression is statically checked and you’re not restricted to variable names, you can also use more complex expressions:

println("Hello ${if (array.size > 0) array[0] else "foo"}!")

Primitive Types

Unlike Java, Kotlin doesn’t distinguish between primitive and wrapper types. You’ll find below the full list of types that correspond to Java primitives:

  • Integer types — Byte, Short, Int, Long
  • Floating-point types — Float, Double
  • Character type — Char
  • Boolean type — Boolean

Since there’s no straightforward equivalent to Java primitives, you may be concerned about efficiency, right? Don’t worry, the Kotlin compiler will ensures to use the most efficient type where applicable: Kotlin’s Double type will be compiled to the Java primitive type double, unless being used in a Collection for example or allowed to be null.

Another important difference between Kotlin and Java, is numeric conversions. Kotlin doesn’t automatically convert numbers from one type to another, the conversion must be explicit:

val i = 42
val l: Long = i
^ Error: Type Mismatch
Unsigned integers support is coming to Kotlin.


Unlike Java, arrays are just classes in Kotlin. Array instances can be created using the arrayOf, arrayOfNulls and emptyArray standard library functions:

val integers: Array<Int> = arrayOf(1, 2, 3)
val strings: Array<String?> = arrayOfNulls(10)
val empty: Array<Double> = emptyArray()

Beware that type arguments of Array always become object types: an Array<Int> will compile to java.lang.Integer[] ! To represent an array of primitive types, Kotlin provide separate array classes, one for each primitive type: IntArray, DoubleArray, BooleanArray and so on:

val threeZeros = IntArray(3)
val threeZerosToo = intArrayOf(0, 0, 0)
val threeZerosAgain = arrayOf(0, 0, 0).toIntArray()


Kotlin’s enums are declared by adding the enum keyword in front of the class header:

Root Type

Similar to Java’s Object, the Any type is the supertype of all non-nullable types in Kotlin. Unlike Java, Any is also the supertype of primitive types such as Int:

val i: Any = 42     // automatic boxing
val j: Any? = null // nullable
Under the hood, the Any type corresponds to java.lang.Object

Type Casts

The is operator checks if an expression is an instance of a type:

fun sizeOf(obj: Any): Int? {
if(obj is String) return obj.length // no cast needed!
if(obj is Array<*>) return obj.size
if(obj is Collection<*>) return obj.size
return null
if(obj !is String) 
print("$obj is not a String)

The as operator tries to cast a value to a type:

fun foo(obj: Any) {
val i = obj as Int // may throw ClassCastException!

The safe cast operator as? tries to cast a value to the specified type and returns null if the value doesn’t have the proper type. The safe cast operator pairs extremely well with the elvis operator:

val stringOrEmpty = obj as? String ?: ""


Kotlin supports both while and do-while loops and doesn’t bring anything new to it.

In Kotlin the for loop construct iterates through anything that provides an iterator but, unlike Java, doesn’t supports any other form than the well-known syntax:

val array = arrayOf("Kotlin", "Java", "Gradle")
for(s in array) println(s)

To iterate over numbers, instead, Kotlin supports the concept of range. As the name implies, a range is just an interval between two values, usually numbers but not limited to:

// inferred type is IntRange
val oneToTen = 1..10
// inferred type is CharRange
val alphabet = 'a'..'z'
// inclusive
for(c in alphabet)
// inclusive
for(i in 0..100)
println("$i of 100")
// exclusive
for(i in 0 until 100)
println("$i of 99")
// downward with step
for (i in 100 downTo 0 step 2)
println("$i left down to 0")
until, downTo and step are not Kotlin keywords but infix extension functions from the standard library (more on this later).

The .. operator, or range operator, when applied to integral types returns an IntRange object which is a subclass of Iterable<Int> over which the for statement iterates. In fact, the 0..100 expression expands to 0.rangeTo(100) under the hood.

Working with arrays or collections, using the destructuring syntax along with the withIndex standard library function or the indices property, you can keep track of the index:

for((index, element) in array.withIndex()) 
println("array[$index] = $element")
for(index in array.indices)
println("array[$index] = ${array[index]}")

Iterating over a map is also easier thanks to the destructuring syntax:

for((key, value) in my_map)
println("map[$key] = $value")

You can use the in operator to check whether a value is in a range, or the opposite:

if(c in 'a'..'z') {
println("$c is a lowercase letter")
} else if(c !in '0'..'9') {
println("$c is not a digit")


Kotlin’s functions declaration starts with the fun keyword, followed by the function name and the parameter list in parentheses. The return type comes after the parameter list, separated by a colon:

Kotlin supports top level functions; you can declare functions at the top level of a file, you don’t need to put them in a class.

Unlike Java, Kotlin supports default parameters and named arguments:

Kotlin’s functions can accept an arbitrary number of argument using the vararg keyword, which pairs extremely well with the spread operator:

Extension Functions

An extension function is a function that can be called as a member of a class, but is defined outside of it:

package strings
fun String.last(): Char = get(length - 1)
fun String.isYelling(): Boolean = last() == '!'
// extension for nullable type
fun String?.isNull(): Boolean = this == null

Extension functions do not have access to private or protected members of the receiver class, and doesn’t automatically become available across your entire project, it needs to be imported just like any other class or function:

import strings.last
import strings.isYelling
// or
import strings.*
val c = "Kotlin".last()
val b = "Java!".isYelling()
val s: String? = null
val ok = s.isNull() // no safe call needed!

You must be aware that extension functions behavior is slightly different from member functions:

  • Member functions always take precedence if they share the same signature.
  • Extension functions resolve to the declared static type of the variable, not to the runtime type of the value:
fun Any.yell() = println("Any!")
fun String.yell() = println("String!")
val str : String = "Hello"
val obj : Any = str
str.yell() // String!
obj.yell() // Any!

Infix Functions

Functions marked with the infix keyword can also be called by omitting the dot and the parentheses:

Kotlin’s standard library already provides a few infix functions, not to be confused with keywords:

infix functions must be member functions or extension functions, and declare a single parameter with no default value.


In Kotlin, a lambda is always surrounded with curly braces and the arrow separates the argument list from the body. When passed as the last or only argument to a function, more concise constructs are possible:

// Classic
people.filter({ p: Person -> p.age < 18 })
// Lambda as last argument can be moved out of parentheses!
people.filter() { p: Person -> p.age < 18}

// Lambda as the only argument? You can remove parentheses!
people.filter { p: Person -> p.age < 18 }
// Inferred parameter type
people.filter { p -> p.age < 18 }
// Using the default parameter name
people.filter { it.age < 18 }

Unlike Java, you can access non-final variables and even modify them:

var empty = 0
strings.forEach { element -> if(element.isEmpty()) empty++ }

Similar to how extension functions works, you can also declare the receiver of a lambda by prefixing its declaration with the type of the receiver:

The buildString helper above could be implemented using the Kotlin’s standard library with function, which also makes use of lambdas with receiver:

Inline Functions

Functions declared with the inline modifier are directly substituted into places where the function is called instead of being invoked:

As a side effect, a bare return statement in a lambda called from an inline function will return from the enclosing function:

Instead, you can use returns with labels:


The when construct may looks a lot like the switch statement, but it is more powerful:

Arbitrary expressions can be used as branch conditions:


Use the throw exception to throw an exception object, but remember there’s no new keyword in Kotlin:

throw Exception("Oops!")

The try..catch block is now an expression:

In Kotlin, all exceptions are unchecked, meaning that the compiler doesn’t force you to catch any of them:

// Java would requires us to catch IOException here


Like Java, classes in Kotlin are declared using the class keyword, but curly braces are optional if there’s no body. Unlike Java, Kotlin doesn’t have a new keyword:

class Empty
class Person { ... }
val p = Person()


A class in Kotlin can have a single primary constructor, and one (or more) secondary constructors. The primary constructor goes after the class name:

Secondary constructors are declared in the class body using the constructor keyword:


Kotlin’s common superclass is Any, that’s also the default for a class with no supertypes declared. To declare an explicit supertype, place the type after a colon in the class header:

// the base class primary constructor must be called (if any)
class Derived(p: Int) : Base(p)
// multiple supertypes are separated by a comma
class Derived: Base(), Listener


As you may have noticed above, properties in Kotlin can be declared as mutable using the var keyword, or immutable using val, and can also be declared using the primary constructor:

class Point {
var x: Double
var y: Double
// equivalent to
class Point(var x: Double, var y: Double) 

Properties in Kotlin can have getters and setters, also known as custom accessors:

Using the by keyword, properties can be delegated to an instance through the setValue and getValue operator:

The Kotlin standard library provides the lazy function that takes a lambda and returns an instance of Lazy<T> and can serve as a delegate:

As a convenience, Kotlin also provides the lateinit modifier to leave non-null properties un-initialized:

Access Modifiers

Unlike Java, Kotlin’s default modifiers are public and final, and you’ll have to explicitly mark classes and members with the open modifier if required. Kotlin also introduced the internal modifier, which restricts accesses to the current module only, while the private modifier is now allowed for top-level declarations:


Interfaces in Kotlin can contains abstract properties along with method declarations and implementations:

Thanks to Kotlin’s built-in support for delegation, a derived class implementing a given interface can delegates all (or some) of its public members to a specified object using the by keyword:

Extension Properties

Similar to extension functions, extension properties provides a way to extend classes using the property syntax instead of the function syntax:

val String.last: Char
get() = get(length - 1)
var StringBuilder.last: Char
get() = get(length - 1)
set(value: Char) {
setCharAt(length - 1, value)

Operator Overloading

Kotlin uses convention methods instead of relying on types to define implementations for a predefined set of operators. To implement an operator, you can provide a member function or an extension function. Functions that overload operators need to be marked as operator:

You’ll find below the list of the predefined operators, excluding delegated properties operators for brevity:

║ Expression ║ Translate to ║
║ a + b ║ ║
║ a - b ║ a.minus(b) ║
║ a * b ║ a.times(b) ║
║ a / b ║ a.div(b) ║
║ a % b ║ a.rem(b) ║
║ +a ║ a.unaryPlus() ║
║ -a ║ a.unaryMinus() ║
║ !a ║ a.not() ║
║ a++ ║ ║
║ a-- ║ a.dec() ║
║ a..b ║ a.rangeTo(b) ║
║ a in b ║ b.contains(a) ║
║ a !in b ║ !b.contains(a) ║
║ a[i] ║ a.get(i) ║
║ a[i, j] ║ a.get(i, j) ║
║ a[i, ..., z] ║ a.get(i, ..., z) ║
║ a[i] = b ║ a.set(i, b) ║
║ a[i, j] = b ║ a.set(i, j, b) ║
║ a[i, ..., z] = b ║ a.set(i, ..., z, b) ║
║ a() ║ a.invoke() ║
║ a(i) ║ a.invoke(i) ║
║ a(i, j) ║ a.invoke(i, j) ║
║ a(i, ..., z) ║ a.invoke(i, ..., z) ║
║ a+=b ║ a.plusAssign(b) ║
║ a-=b ║ a.minusAssign(b) ║
║ a*=b ║ a.timesAssign(b) ║
║ a/=b ║ a.divAssign(b) ║
║ a%=b ║ a.remAssign(b) ║
║ a == b ║ a?.equals(b) ?: (b === null) ║
║ a != b ║ !(a?.equals(b) ?: (b === null)) ║
║ a > b ║ a.compareTo(b) > 0 ║
║ a < b ║ a.compareTo(b) < 0 ║
║ a >= b ║ a.compareTo(b) >= 0 ║
║ a <= b ║ a.compareTo(b) <= 0 ║
║ for(a in b) ║ b.iterator() ║
║ val (x, y, z) = a ║ val x = a.component1() ║
║ ║ val y = a.component2() ║
║ ║ val z = a.component3() ║
Both inc and dec operators must return a value and shouldn’t mutate the object.

Data Classes

Kotlin provides autogenerated equals, hashCode, toString and copy methods implementations for free by adding the data modifier to your class:

data class Person(val name: String, val age: Int)
val john = Person("John", 21)
println(john) // "Person(name=John, age=21)"
val bob = john.copy(name="Bob")
println(bob) // "Person(name=Bob, age=21)"
println(bob == bob.copy())  // true

Nested Classes

Unlike Java, inner classes in Kotlin don’t have access to the outer class instance, unless explicitly requested by adding the inner modifier. Kotlin’s default behavior is equivalent to a static nested class in Java, but an outer class doesn’t see private members of its inner (or nested) classes:

Sealed Classes

Sealed classes are used to represent restricted class hierarchy: a sealed class can have subclasses, but all of them must be declared in the same file as the sealed class itself:

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()

A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.

Classes which extend subclasses of a sealed class (indirect inheritors) can be placed anywhere, not necessarily in the same file.

The object keyword

The object keyword defines a class and creates an instance of that class at the same time. The singleton pattern comes immediately in mind as a use case:

Anonymous objects replace Java’s anonymous inner classes but, unlike Java, they can implement multiple interfaces:

Classes in Kotlin can’t have static members since there’s no such keyword in the Kotlin language. Instead, top-level and object declarations are recommended but if you need to access private members of an outer class, companion objects are the way to go:


There’s still quite a few unexplored corners like coroutines, generics or java interoperability but, if you come that far, you should be ready to switch to Kotlin.

Do not forget to dive into the Koans exercises to practice what you learned, while keeping an hand at the official documentation. You can also write and execute Kotlin’s snippets online!

That guide will be updated over time and suggestions, so do not hesitate to follow me? Thanks for reading!

The above article has been written with ❤ ; Feel free to share and don’t forget to clap if you liked it!