Kotlin is a rising programming language that aims to address the flaws of Java and be more seamlessly tied with the adaptable needs of the modern world. While once a niche language, Kotlin is quickly growing and has many companies switching from Java to Kotlin.
In fact, Google announced in 2017 that it would officially support Kotlin, rather than Java, as the main language for Android development.
Now, more and more companies are looking for Kotlin developers. Today, we’ll help you transition to this new language by highlighting the differences and syntax of each fundamental Kotlin concept.
Get right to what you need without wasting time on programming fundamentals you already know.
Kotlin Crash Course for Programmers
Kotlin is an open-source, statically typed programming language developed by Jetbrains, which is famous for creating the IntelliJ IDEA IDE. The goal of Kotlin is to be an easier-to-use, more concise language for any type of project.
Kotlin is mainly used for Android app development or front-end development with frameworks like React.
Kotlin is unique because it is completely interoperable with Java code, meaning you can use existing Java libraries or frameworks. Kotlin code can transpile to JavaScript or Java bytecode for use on the Java Virtual Machine (JVM) or Android development.
Modern companies are particularly interested in Kotlin’s versatility because developers can use the same language for any project the company needs.
As agile development becomes more prevalent, companies like Google, Amazon, Netflix, Uber have started hiring Kotlin developers. In fact, it was ranked 4th fastest growing language in 2020 in Github’s yearly report, with and many more reporting implementations of Kotlin developers.
var m : Int = 12
m = 10 // ok
m = "twelve" // error!
m = 10.0 // error!
fun main() {intArrayOf(4, 5, 6).forEach lambda@ {if (it == 5) return@lambdaprintln(it)}println()loop@ for (i in 0 .. 3) {for (j in 0 .. 3) {if (i + j == 4) continue@loopif (i + j == 5) break@loopprintln(i + j)}}}
val languageArray = arrayOf("Serbian", "Swahili", "Japanese", "German", "Spanish")
val selectedLang = languageArray
.filter { name -> name.startsWith("s", ignoreCase = true) }
//or: .filter { it.startsWith("s", ignoreCase = true) }
.sortedBy { name -> name.length }
.first()
class Person {
var firstName = ...
var lastName = ...
}
// Maybe the author of the Person class forgot to include this
fun Person.setName(firstName: String, lastName: String) {
this.firstName = firstName
this.lastName = lastName
}
people.filter { person -> person.headCount == 1 }
Kotlin
Allows cross-platform transpiling from Kotlin to Android, JVM, and more.
Built from the ground up for both functional programming and object-oriented programming.
Designed for conciseness.
Built-in smart casting and implicit casting.
Cannot assign null values to ensure null safety
Out-of-the-box support for modern features like coroutines, extension functions, and data classes.
Java:
Not cross-platform, cannot transpile.
Built for object oriented programming with extremely limited functional support.
Designed for specificity.
Strict explicit typing rules.
Allows nulls but throws nullPointerException, which must be caught with exception handling.
Limited support for modern features through complex workarounds.
Transition to Kotlin in half the time with lessons tailored to your current developer skills. Educative’s hands-on courses let you learn the tools you need to advance your career without starting back from square one.
Let’s take a look at a Hello World program in both Java and Kotlin to see the differences.
You can write Kotlin in any Java IDE using the Kotlin plugin or even directly on the command-line.
class HelloWorld {public static void main(String[] args) {System.out.println("Hello, World!");}}
Clearly, Kotlin is more concise, only using two lines of code. This is because Kotlin allows functions to exist outside of classes, meaning we don’t have to declare a HelloWorld class.
On line 1 of the Kotlin code, we define the main method. Like Java, every program needs a main function. Kotlin version 1.3+ removed inputs and outputs from main to make a simpler and less visually cluttered syntax.
On line 2, we print the string Hello, World! using the built-in println() function. This works the same way as Java’s print function but does not need to be pulled from an external System class.
At the end of line 2, notice there is no semicolon to mark the line’s end. Kotlin detects the end of statements by line and does not require semicolons.
You can define Kotlin variables using either the var or val keywords.
var <variableName> = <value> //mutable
val <variableName> = <value> //read-only
val <variableName>: <dataType> = <value> // explicit type casting
Variables in Kotlin are very similar to variables in Java except:
Kotlin offers immutable (read-only) data types for functional programming
Kotlin allows for smart typing, sometimes called implicit typing, where it automatically detects the data type of a variable without explicit tagging.
Kotlin does not allow you to assign a null value to variables or return values by default. With Java, you can assign null, but it will throw a nullPointerException if you try to access that value.
Let’s see how these differences look in practice.
Variables in Kotlin can be either read-only or mutable. The value of read-only variables cannot be changed after they’ve been initially assigned. Read-only variables are especially helpful in reducing side effects when using functional programming.
Mutable variables are like most variables in Java and can be changed after they’ve been initialized.
Read-only initialization:
val number = 17
println("number = $number")
number = 42 // Not allowed, throws an exception
Mutable initialization:
var number = 17
println("number = $number")
number = 42 // var can be reassigned
println("number = $number")
It’s best practice to use read-only val variables whenever possible and only use mutable var when you specifically need it. This minimizes the overall complexity of your programs and makes it easier to understand their data flow.
Unlike Java, Kotlin does not have primitive data types. All the following types are objects at runtime but do transpile to the respective Java primitive types in Java bytecode.
Integer Types
Kotlin includes all standard integer types. Below examples of explicitly cast, read-only integers that are initialized to their greatest possible values.
val byte: Byte = 127
val short: Short = 32767
val int: Int = 2147483647
val long: Long = 9223372036854775807
Floating-Point Numbers
Kotlin supports standard decimal and exponent notations. Float and Double behave the same, but Double can hold more numbers than Float. Kotlin smart casts any floating-point number to Double, meaning you have to include f at the end of the argument to set a variable as a Float.
val float: Float = 3.4028235e38f
val double: Double = 1.7976931348623157e308
Text Types
Kotlin supports Java-like Char and String types to represent text. You denote single characters with single quotes,'c', and Strings with double quotes “this string”`.
val character: Char = '#'
val text: String = "Learning about Kotlin's data types"
Unlike Java, Kotlin allows you to easily make multi-line strings using three sets of double quotes, """<multipleLines>""". Java only offers concat and line.separator for multi-line strings, which are less concise and require special implementations.
Boolean Type
Kotlin’s Boolean type is identical to Java’s in function.
val yes: Boolean = true
val no: Boolean = false
You can use the
askeyword for type conversion of one type to another.
Type inference
Type inference is a compiler feature that allows you to omit types in your code when the compiler can infer it for you. This is also sometimes called smart typing or implicit typing.
If a variable could be declared as two types of differing sizes, it will choose the default: Double for floating-point numbers and Int for integers.
To implicitly declare non-default types, you can add the type key at the end of the value, like L for type Long below:
val string = "Educative"
val int = 27
val long = 42L
val double = 2.71828
val float = 1.23f
val bool = true
Kotlin’s type inference system is very robust and allows implicit typing of literals, objects, and complex structures, like lambda expressions.
Kotlin includes two conditional statements, if and when, and two loops, for and while.
All conditionals are defined using:
<conditional> (<desiredCondition>) <code>
<conditional> (<desiredCondition>) { <codeBlock>
}
If statement
Kotlin if statement is just like Java’s in that it contains a code block which executes if the listed condition becomes true. You can also add an else statement that executes if the if statement is skipped.
var max = a
if (a < b) max = b
// With else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// As expression
val max = if (a > b) a else b
When Statement
Kotlin also has a when statement that works similarly to C’s switch statement. The when statement creates multiple branches of checks, which each evaluated for a particular condition.
when (x) {
1 -> print("x == 1") //branch 1
2 -> print("x == 2") //branch 2
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
when can be used either as an expression or as a statement. If it is used as an expression, the value of the first matching branch becomes the value of the overall expression. If it is used as a statement, the values of individual branches are ignored.
You can think of when as multiple if-else statements rolled into a more concise format.
For Loop
The Kotlin for loop works like a C for each loop rather than Java’s for loop. It accepts a collection, like an array, and completes the same action on each element of the collection.
The syntax for a 1 for loop is:
for (item in collection) print(item)
for (i in 1..3) {
println(i)
}
While Loop
The while loop executes the body of code repeatedly so long as the listed conditions remain met. Like Java, there are two types of while loop. The standard while loop checks for the condition before the code is executed, and the do-while loop checks after the code is executed.
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y is visible here!
Kotlin includes 3 basic collections: lists, sets, and arrays. Like Java, they each act as frameworks to store data in different ways. Collections are assigned to variables like any other value.
You can create all collections in Kotlin using their unique constructor function. All constructors use the same naming convention, <collection>Of(<value1, value2, …>). For example, you can make a list of 2 integers with listOf(1, 2).
List
Lists are indexed linear data structure similar to Java arrays. The indices start at 0 and go to (list.size - 1). Unlike arrays, lists are dynamic and can resize to accommodate added or removed elements. Lists can contain any number of duplicate elements.
val numbers = listOf("one", "two", "three", "four")
println("Number of elements: ${numbers.size}")
println("Third element: ${numbers.get(2)}")
println("Fourth element: ${numbers[3]}")
println("Index of element \"two\" ${numbers.indexOf("two")}")
Sets
A set is an unordered linear data structure that cannot contain duplicate elements. Sets do not have indices. Two sets are considered equal if both contain all the same unique elements.
val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
if (numbers.contains(1)) println("1 is in the set")
val numbersBackwards = setOf(4, 3, 2, 1)
println("The sets are equal: ${numbers == numbersBackwards}")
The above sets are equal despite appearing in the opposite order in initialization.
Array
The array is not a native data type in Kotlin. It is represented by the Array class. These arrays behave the same as Java arrays: both contain mutable values but are fixed size.
You can create an array using the function arrayOf(<element1, element2, …>).
For example:
val priorities = arrayOf("HIGH", "MEDIUM", "LOW")
println(priorities[1])
priorities[1] = "NORMAL"
println(priorities[1])
println(priorities.size)
Functions in Kotlin require you to define the function’s name, inputs, and outputs. All function definitions begin with the fun keyword and contain a block of code executed when the function is called. Unlike Java, you do not need to define the method as static, as Kotlin is less strictly tied to OOP.
Here’s an example of Kotlin’s function syntax:
fun <variableName>(<inputName>: <inputType>): <returnType>
fun fibonacci(index: Int): Long
To mimic the behavior of Java’s
voidreturn types, you can set the return type toNothing.
One of Kotlin and Java’s biggest differences is that functions can exist outside of classes in Kotlin. On the other hand, Java is rigidly bound to object-oriented programming and requires everything to be within class definitions.
Package-level functions (functions outside of any particular class) are used to hold tasks equally useful for any object in the program.
For example, a convert function that converts kg to lbs and lbs to kg would be a good package-level function because any class in the program might have to convert measurements.
Kotlin also allows for higher-order functions, meaning functions can be passed or returned like other values. This is invaluable for functional programming designs.
Kotlin adapts the standard OOP formula with Extension Functions, which let you extend the functionality of existing classes without inheriting from them. Extension functions are useful when trying to use the functionality of third-party libraries you can’t modify but can extend. Unlike inheritance, you can extend a class without full access to it.
For example, you could add additional behavior to an external API that would help closer integrate it with your solution.
To declare an extension function, we need to prefix its name with a receiver type, i.e. the type being extended. The following adds a swap function to MutableList –
fun MutableList < Int > .swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
The
thiskeyword is used to reference values and methods from the extended type.
The most significant difference between Kotlin and Java is that Kotlin is tailored for functional programming, not just OOP. Functional programming in Java is limited. While you can mimic many functional behaviors in Java, most developers agree that it’s not applicable in the real world.
Kotlin is built from the ground up to be a fully featured functional language, with higher-order functions, lambda expressions, operator overloading, lazy evaluation, operator overloading, and more.
// higher order functionsfun main( ) {val numbers = arrayListOf(15, -5, 11, -39)val nonNegativeNumbers = numbers.filter{it >= 0}println(nonNegativeNumbers) //function as input parameter}
Kotlin has evolved significantly since its early days, and understanding these changes is essential for Java developers making the transition. The most important milestone is the K2 compiler, which became stable with Kotlin 2.0 and is now the default in 2.2.x.Here’s why it matters:
Faster compilation: K2 dramatically improves build speeds, reducing developer wait times and making continuous integration pipelines faster.
Better diagnostics: More precise error messages, smarter type inference, and improved compiler hints make debugging much more efficient.
Language evolution: K2 lays the groundwork for new features like context receivers, improved multiplatform support, and future Kotlin enhancements.
Tooling improvements: IDEs like IntelliJ IDEA and Android Studio now have deeper K2 support, providing better auto-completion, refactoring, and static analysis.
If you’re transitioning from Java today, adopting Kotlin 2.x or later ensures you’re using the latest language features, compiler optimizations, and developer tools.
Kotlin revolutionizes concurrency with coroutines, making asynchronous code simpler, safer, and more readable than traditional Java approaches.
Where Java relies on CompletableFuture, thread pools, or ExecutorService, Kotlin uses structured concurrency and non-blocking APIs to eliminate complexity.
Asynchronous execution |
|
+
|
Threading model | Blocking I/O | Structured concurrency (launch, async) |
Reactive programming | Reactive Streams |
for asynchronous data streams |
Structured concurrency: Coroutines automatically manage scope and cancellation, preventing leaks and orphaned threads.
Suspend functions: Functions marked suspend can pause and resume without blocking threads.
Flows: Kotlin’s reactive streams API supports backpressure and composability, perfect for real-time data processing.
Integration: Coroutines interoperate seamlessly with Java concurrency APIs, enabling incremental adoption.
Kotlin has evolved rapidly, introducing features that make code more expressive and maintainable.
Sealed interfaces: Enable exhaustive when checks for safer state modeling.
Value classes: Create zero-cost wrappers for primitive types with compile-time safety.
Data objects: Lightweight, singleton-like versions of data classes for representing immutable state.
Inline functions with reified types: Allow type-safe generics without type erasure.
Context receivers (experimental): Pass implicit context objects for cleaner APIs.
These features align Kotlin closely with functional programming principles while retaining full Java interoperability.
Kotlin’s null safety system eliminates most NullPointerExceptions — a major pain point in Java.
Beyond syntax, understanding null safety means using the right operators and patterns effectively.
Safe calls (?.): Safely access properties without throwing exceptions.
Elvis operator (?:): Provide default values when encountering nulls.
Non-null assertions (!!): Use sparingly — only when null is truly impossible.
Platform types: Use @Nullable and @NotNull in Java for safer Kotlin interop.
Nullable collections: Kotlin distinguishes between List<String?> and List<String>, giving finer control over nullability.
With these tools, your Kotlin code becomes safer, cleaner, and more predictable.
One of Kotlin’s biggest advantages is smooth interoperability with existing Java codebases.
You can gradually migrate to Kotlin without rewriting your entire project.
@JvmOverloads: Generates overloads for default parameters.
@JvmStatic / @JvmField: Ensures static members behave as Java expects.
SAM conversions: Use Kotlin lambdas directly with Java functional interfaces.
Extension functions: Appear as static methods in Java but feel idiomatic in Kotlin.
Companion objects: Map naturally to Java’s static members.
These features make it easy for Kotlin and Java to coexist in the same codebase.
Kotlin now extends far beyond Android — it’s a full cross-platform ecosystem.
Kotlin Multiplatform (KMP): Share business logic across Android, iOS, desktop, and web.
Compose Multiplatform: Build declarative UIs for multiple platforms with one codebase.
Wasm & JS targets: Compile Kotlin to JavaScript or WebAssembly for browser or embedded environments.
Multiplatform libraries: Use shared libraries like ktor, SQLDelight, and kotlinx.serialization.
For Java developers, this means you can create cross-platform apps without leaving the JVM ecosystem.
If you’re coming from Java-based Android development, the ecosystem has changed — Kotlin is now the default.
Jetpack Compose: Kotlin’s declarative UI toolkit replaces XML layouts, improving productivity and performance.
Gradle Kotlin DSL: Build scripts written in Kotlin offer type safety and better IDE integration.
Tooling & ecosystem: Most modern Android SDKs, libraries, and samples are Kotlin-first.
Compiler optimizations: Kotlin’s compiler produces faster builds and smaller APKs.
Migrating existing projects to Kotlin enhances maintainability, scalability, and future compatibility as Android evolves.
By embracing coroutines, modern language features, and multiplatform capabilities, Kotlin empowers developers to build safer, faster, and more scalable applications — all while staying compatible with the Java ecosystem.
Congratulations on taking your first steps with Kotlin! This rising language is perfect for modern developers and is easy to pick up for existing Java developers.
You’re now ready to move on to intermediate topics such as:
Nullable types
Conditions as expressions
Map collections
Shorthand function notation
Operator functions
To help you transition to Kotlin fast, Educative has made Kotlin Crash Course for Programmers. This course is tailored towards existing Java developers, meaning all lessons are hands-on and highlight what’s different about the language. By the end, you’ll know how to use all the basic features of Kotlin and will be ready to build your first Android application with Android Studio or another Kotlin program
Happy learning!