What Is Kotlin?
Kotlin is a JVM-based programming language developed by JetBrains. While being 100% interoperable with Java, it offers modern features such as concise syntax, null safety, and functional programming support. It has grown rapidly since Google adopted it as the official language for Android development.
This article covers the key syntax differences that Java developers should know when transitioning to Kotlin.
Variable Declaration and Type Inference
Kotlin declares variables with val (immutable) and var (mutable). Type inference is powerful enough that you rarely need to explicitly specify types.
fun main() {
// val — Immutable (like Java's final)
val name = "John Doe" // Inferred as String
val age: Int = 25 // Explicit type declaration
// name = "Jane Smith" // Compile error! val cannot be reassigned
// var — Mutable
var score = 85
score = 92 // Reassignment allowed
println("$name (age $age), score: $score")
// Output: John Doe (age 25), score: 92
// String templates — ${expression}
val items = listOf("apple", "banana", "grape")
println("${items.size} fruits: ${items.joinToString(", ")}")
// Output: 3 fruits: apple, banana, grape
// Type conversion — Unlike Java, explicit conversion is required
val intValue = 42
val longValue: Long = intValue.toLong() // No auto-conversion
val doubleValue: Double = intValue.toDouble()
println("Int: $intValue, Long: $longValue, Double: $doubleValue")
// Output: Int: 42, Long: 42, Double: 42.0
}
In Kotlin, using val by default is recommended — only use var when reassignment is truly necessary.
Null Safety
Kotlin’s most important feature is type-system-level null safety. By default, all types do not allow null. Nullable types are explicitly marked with ?.
fun main() {
// Default types cannot be null
var name: String = "John Doe"
// name = null // Compile error!
// Nullable types use ?
var nickname: String? = "Johnny"
nickname = null // Allowed
// Safe call operator (?.) — Returns null if null
println("Nickname length: ${nickname?.length}")
// Output: Nickname length: null
// Elvis operator (?:) — Default value when null
val displayName = nickname ?: "No name"
println("Display name: $displayName")
// Output: Display name: No name
// Safe call chaining
val city: String? = getUser()?.address?.city
println("City: ${city ?: "Unknown"}")
// Output: City: Unknown
// let — Execute block only when non-null
val email: String? = "user@example.com"
email?.let {
println("Sending email: $it")
// Output: Sending email: user@example.com
}
// Non-null assertion (!!) — Can throw NullPointerException, use sparingly
// val length = nickname!!.length // NPE thrown!
}
// Example functions
data class Address(val city: String?)
data class User(val name: String, val address: Address?)
fun getUser(): User? = null
The !! operator throws NullPointerException if the value is null, so avoid it when possible. Use ?. and ?: together for safe handling.
Data Classes and when Expressions
Kotlin’s data class automatically generates equals(), hashCode(), toString(), and copy(). You can define DTO/VO classes that would take dozens of lines in Java in just one line.
// Data class — auto-generates equals, hashCode, toString, copy
data class Product(
val name: String,
val price: Int,
val category: String = "General" // Default parameter
)
// Sealed class — Restricted hierarchy
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String, val code: Int) : Result()
data object Loading : Result()
}
fun handleResult(result: Result): String {
// when — Enhanced version of Java's switch (usable as an expression)
return when (result) {
is Result.Success -> "Success: ${result.data}"
is Result.Error -> "Error [${result.code}]: ${result.message}"
is Result.Loading -> "Loading..."
// No else needed with sealed class (all cases covered)
}
}
fun main() {
// Using data classes
val product1 = Product("Keyboard", 89000)
val product2 = Product("Keyboard", 89000)
println(product1) // Auto-generated toString
// Output: Product(name=Keyboard, price=89000, category=General)
println("Equality: ${product1 == product2}") // Auto-generated equals
// Output: Equality: true
// Create a copy with only some fields changed
val discounted = product1.copy(price = 79000)
println("Discounted: $discounted")
// Output: Discounted: Product(name=Keyboard, price=79000, category=General)
// Destructuring declarations
val (name, price, category) = product1
println("$name — $price ($category)")
// Output: Keyboard — 89000 (General)
// when expression
val results = listOf(
Result.Success("Data loaded"),
Result.Error("Network error", 500),
Result.Loading
)
results.forEach { println(handleResult(it)) }
// Output:
// Success: Data loaded
// Error [500]: Network error
// Loading...
}
Extension Functions
Extension functions add new functions to existing classes without modifying them. They replace Java utility classes and improve code readability.
// Add extension function to String
fun String.toSlug(): String {
return this.lowercase()
.replace(Regex("[^a-z0-9\\s-]"), "")
.replace(Regex("\\s+"), "-")
.trim('-')
}
// Add extension function to List
fun <T> List<T>.secondOrNull(): T? {
return if (size >= 2) this[1] else null
}
// Add extension property to Int
val Int.formatted: String
get() = String.format("%,d", this)
fun main() {
// Using extension functions — as if they were original methods
val title = "Hello World! Kotlin Guide"
println(title.toSlug())
// Output: hello-world-kotlin-guide
val items = listOf("first", "second", "third")
println("Second: ${items.secondOrNull()}")
// Output: Second: second
val emptyList = emptyList<String>()
println("Second: ${emptyList.secondOrNull()}")
// Output: Second: null
// Using extension property
println(89000.formatted)
// Output: 89,000
println(1500000.formatted)
// Output: 1,500,000
}
Summary
Here is a comparison of Kotlin’s key advantages over Java.
- Null safety: The
?,?., and?:operators prevent NPE at compile time. - Conciseness: Define DTOs in a single line with
data class, and reduce boilerplate with type inference. - when expression: More powerful than
switch, and combined withsealed class, it works like pattern matching. - Extension functions: Add methods to existing classes, eliminating utility classes.
- Default parameters: Replace Java’s method overloading with default values.
You can gradually introduce Kotlin into existing Java projects, so starting with new files in Kotlin is recommended.