CS 3100: Program Design and Implementation II
Lecture 36: Kotlin: A Better Java
©2026 Ellen Spertus, CC-BY-SA
Learning Objectives
After this lecture, you will be able to explain the benefits of these Kotlin features:
- Nullable types
- Named and default arguments
- Top-level functions
- Type inference
- Conditional expressions
-
ifexpressions -
switchexpressions
Semester Grades: Labs
| Grade | Total Points | Individual Assignments | Group Assignments | Exams | Labs | Participation |
|---|---|---|---|---|---|---|
| A | ≥900 | ≥240 (80%) | ≥160 (80%) | ≥280 (70%) | ≥11 completed | ≥40 (80%) |
| B | ≥800 | ≥210 (70%) | ≥140 (70%) | ≥220 (55%) | ≥9 completed | ≥25 (50%) |
| C | ≥700 | ≥180 (60%) | ≥120 (60%) | ≥200 (50%) | ≥7 completed | — |
| D | ≥600 | — | — | — | — | — |
| F | <600 or fails to meet above minimums |
There are 14 labs; each is worth 5 points, with the total capped at 50 points (allowing you to miss a few without penalty).
For example, if you attend 10 labs, you get 50 points and can earn a B (possibly a B+).
Poll: Why do/don't you attend class/lecture when ungraded?

Text espertus to 22333 if the
URL isn't working for you.
This poll is anonymous but counts toward participation.
Poll: What's your least favorite thing about Java?

Text espertus to 22333 if the
URL isn't working for you.
This is a word cloud poll, so answer briefly, with hyphens separating words (e.g., null-pointers)
Common Criticisms of Java
-
Verbosity and boilerplate code
-
Everything must be in a class
-
Lack of null-safety
-
Checked exceptions
What Is Kotlin?
- Created by JetBrains (makers of IntelliJ IDEA), released 2016
- Designed to be fully interoperable with Java — runs on the JVM
- Goals: fix Java's pain points while staying familiar to Java developers
- In 2017, Google named it a first-class language for Android
- Today used in Android, backend (Spring), and multiplatform development
Java and Kotlin on the JVM
Java
static int cube(int x) {
return x * x * x;
}
public static void main(String[] args) {
System.out.println(cube(3));
}
Kotlin
fun cube(x: Int) = x * x * x
fun main() {
println(cube(3))
}
Byte Code
static cube(I)I public static main(Ljava/lang/String;)V
ILOAD 0 L0
ILOAD 0 LINENUMBER 8 L0
IMUL GETSTATIC System.out : Ljava/io/PrintStream;
ILOAD 0 ICONST_3
IMUL INVOKESTATIC Main.cube (I)I
IRETURN INVOKEVIRTUAL java/io/PrintStream.println (I)V
What differences can you spot?
Java
public class MyClass {
static int cube(int x) {
return x * x * x;
}
public static void main(String[] args) {
System.out.println(cube(3));
}
}
Kotlin
fun cube(x: Int) = x * x * x
fun main() {
println(cube(3))
}
Some Differences
Java
public class MyClass {
static int cube(int x) {
return x * x * x;
}
public static void main(String[] args) {
System.out.println(cube(3));
}
}
Kotlin
fun cube(x: Int) = x * x * x
fun main() {
println(cube(3))
}
- No semicolons
- Code outside class (top-level functions)
- Visibility defaults to
public funkeyword- Different parameter lists
- name followed by
:and type - all types are capitalized (objects)
- name followed by
- A function body can be an expression
- Return type can be inferred
Transforming describeItems()
Java
// describeItems(1, "rock") => "1 rock"
// describeItems(2, "rock") => "2 rocks"
static String describeItems(
int quantity, String item) {
String description = quantity + " " + item;
if (quantity != 1) {
return description + "s";
} else {
return description;
}
}
public static void main(String[] args) {
System.out.println(describeItems(1, "rock"));
System.out.println(describeItems(2, "rock"));
}
Kotlin
// describeItems(1, "rock") => "1 rock"
// describeItems(2, "rock") => "2 rocks"
fun describeItems(
quantity: Int, item: String): String {
val description = quantity.toString() + " " + item
if (quantity != 1) {
return description + "s"
} else {
return description
}
}
fun main() {
println(describeItems(1, "rock"))
println(describeItems(2, "rock"))
}
Kotlin variables are declared with
val(immutable)var(mutable)
Transformation
fun describeItems(
quantity: Int, item: String): String {
val description =
quantity.toString() + " " + item
if (quantity != 1) {
return description + "s"
} else {
return description
}
}
fun describeItems(
quantity: Int, item: String) =
"$quantity $item" +
if (quantity != 1) "s" else ""
Changes:
- String templates
- Return type omitted
- Return statement omitted
if-expression value used
You Transform 3-Argument Max
Java
static int max(int a, int b, int c) {
if (a >= b && a >= c) {
return a;
} else if (b >= c) {
return b;
} else {
return c;
}
}
Kotlin
fun max(a: Int, b: Int, c: Int): Int {
if (a >= b && a >= c) {
return a
} else if (b >= c) {
return b
} else {
return c
}
}
Transformation
Before
fun max(a: Int, b: Int, c: Int): Int {
if (a >= b && a >= c) {
return a
} else if (b >= c) {
return b
} else {
return c
}
}
After
fun max(a: Int, b: Int, c: Int) =
if (a >= b && a >= c) a
else if (b >= c) b
else c
Nullable Types
In Java, any reference can be null:
String name = null;
System.out.println(name.length()); // NullPointerException at runtime
In Kotlin, non-nullable is the default:
var name: String = null // does not compile
var name: String? = null // ok — question mark indicates nullability
Safe Call Operator ?.
Kotlin won't let you call methods on a nullable type without handling the null case:
var name: String? = null
println(name.length) // does not compile
The safe call operator returns null instead of throwing an exception:
println(name?.length) // prints null
Chains short-circuit at the first null:
println(user?.address?.city) // prints null if any part is null
Exercise: Nullable Types
Java
record Client(PersonalInfo personalInfo) {}
record PersonalInfo(String email) {}
interface Mailer {
void sendMessage(String email, String message);
}
void sendMessageToClient(
@Nullable Client client,
@Nullable String message,
@NotNull Mailer mailer) {
// Try to extract email address
if (client == null) return;
PersonalInfo info = client.personalInfo();
if (info == null) return;
String email = info.email();
if (email == null || message == null) return;
mailer.sendMessage(email, message);
}
Kotlin
class Client(val personalInfo: PersonalInfo?)
class PersonalInfo(val email: String?)
interface Mailer {
fun sendMessage(email: String, message: String)
}
fun sendMessageToClient(
client: Client?,
message: String?,
mailer: Mailer
) {
// Extract email address or set to null
val email = TODO()
// Can't send if there is no email or message
if (email == null || message == null) {
return
}
mailer.sendMessage(email, message)
}
Transformation
Java
void sendMessageToClient(
@Nullable Client client,
@Nullable String message,
@NotNull Mailer mailer) {
// Try to extract email address
if (client == null) return;
PersonalInfo info = client.personalInfo();
if (info == null) return;
String email = info.email();
if (email == null || message == null) return;
mailer.sendMessage(email, message);
}
Kotlin
fun sendMessageToClient(
client: Client?,
message: String?,
mailer: Mailer
) {
val email = client?.personalInfo?.email
if (email == null || message == null) {
return
}
mailer.sendMessage(email, message)
}
Kotlin Koan: Smart Casts
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(expr: Expr): Int =
when (expr) {
is Num -> TODO()
is Sum -> TODO()
else -> throw IllegalArgumentException(
"Unknown expression")
}
https://play.kotlinlang.org/koans/Classes/Smart%20casts/Task.kt
Kotlin Koan: Sealed Classes
fun eval(expr: Expr): Int =
when (expr) {
is Num -> TODO()
is Sum -> TODO()
} a + (b ?: 0) + (c ?: 0)
interface Expr
class Num(val value: Int) : TODO()
class Sum(val left: Expr, val right: Expr) : TODO()
https://play.kotlinlang.org/koans/Classes/Sealed%20classes/Task.kt
The Elvis Operator ?:
The Elvis operator returns a default value when the left side is null:
val name: String? = null
println(name ?: "Unknown") // Unknown
val length = name?.length ?: 0 // 0
This replaces the common null-check pattern:
// Without Elvis
val display = if (name != null) name else "Unknown"
// With Elvis
val display = name ?: "Unknown"
Why "the Elvis Operator"?

Elvis Operator: Example
Java
static int add3(int a, Integer b, Integer c) {
return a
+ (b != null ? b : 0)
+ (c != null ? c : 0);
}
Kotlin
fun add3(a: Int, b: Int?, c: Int?): Int {
var result = a
if (b != null) {
result = result + b
} else {
result = result + 0
}
if (c != null) {
result = result + c
} else {
result = result + 0
}
return result
}
Transformation
Before
fun add3(a: Int, b: Int?, c: Int?): Int {
var result = a
if (b != null) {
result = result + b
} else {
result = result + 0
}
if (c != null) {
result = result + c
} else {
result = result + 0
}
return result
}
After
fun add3(a: Int, b: Int?, c: Int?) =
a + (b ?: 0) + (c ?: 0)
Extension Functions
Can you add a new method to an existing class whose source code you don't have?
Not in Java, but in Kotlin...
fun String.truncate(maxLength: Int): String {
return if (this.length <= maxLength) this else take(maxLength - 3) + "..."
}
// Hint: Use the String method reversed()
fun String.isPalindrome(): Boolean = TODO()
fun main() {
val shortUsername = "KotlinFan42"
val longUsername = "JetBrainsLoverForever"
println("Short username: ${shortUsername.truncate(15)}")
println("Long username: ${longUsername.truncate(15)}")
// println("wow".isPalindrome()) // true
// println("woo".isPalindrome()) // false
}
Poll: Which Kotlin feature excites you most?
A. Null safety (no more NullPointerExceptions)
B. Default and named arguments (no more overloads)
C. Data classes (no more boilerplate)
D. Extension functions (add methods to existing classes)
E. Smart casts (no more explicit casting)
F. String templates (no more concatenation)
G. Conditional expressions (if/when)
H. I don't like any of them

Text espertus to 22333 if the
URL isn't working for you.
Poll: How do you feel about Java?

Text espertus to 22333 if the
URL isn't working for you.
Bonus Slide
