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 how these Kotlin features address Java pain points, such as:
- 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+).
Semester Grades: Attendance/Participation
| Missed Polls | Participation Points |
|---|---|
| 0–6 | 50 (full credit) |
| 7–11 | 42 |
| 12–17 | 32 |
| 18–23 | 20 |
| 24–29 | 10 |
| 30+ | 0 |
Participation Grading: You may miss up to 6 classes (or participating in 6 days' activities) with no penalty. Beyond 6 absences, your participation score decreases on a non-linear scale—missing a few extra classes has minimal impact, but missing many has a larger effect.
This scale is designed so that missing up to half the semester's lectures (17 classes) still qualifies you for a B in participation. However, students who attend regularly perform significantly better on exams, and missing all 50 participation points means you must score higher elsewhere to reach your target grade.
If you received a 0 for attendance on a day you think you participated, check your PollEV account, which lists all your responses. Send me a screenshot showing if you completed at least 50% of surveys.
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 was your favorite puzzler?
A. Time for a Change: System.out.println(2.00 - 1.10);
B. A Change is Gonna Come: BigDecimal payment = new BigDecimal(2.00);
C. Animal Farm: "Animals are equal: " + pig == dog
D. Histogram Mystery: Math.abs(pair.hashCode()) % histogram.length
E. Ping Pong: t.run()
F. Long Division: long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
G. It's Elementary: System.out.println(12345 + 5432l); System.out.println(01234 + 43210);
H. Dang, I missed it.

Text espertus to 22333 if the
URL isn't working for you.
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
A Closer Look
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))
}
- 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
Kotlin Koans
An interactive Kotlin tutorial
https://play.kotlinlang.org/koans/Introduction/Hello,%20world!/Task.kt
Complete Koan 1: Hello, world!
Remember Telescoping Constructors (L17)?
public class Recipe {
// Telescoping constructors — each adds one more parameter
Recipe(String name) { this(name, List.of(), List.of(), List.of(), null, false); }
Recipe(String name, List<Ingredient> ingredients) { this(name, ingredients, List.of(), List.of(), null, false); }
Recipe(String name, List<Ingredient> ingredients, List<String> instructions) { ... }
Recipe(String name, List<Ingredient> ingredients, List<String> instructions, List<String> notes) { ... }
Recipe(String name, List<Ingredient> ingredients, List<String> instructions, List<String> notes, String source) { ... }
Recipe(String name, List<Ingredient> ingredients, List<String> instructions, List<String> notes, String source, boolean isVegan) { ... }
}
Default Arguments
Java
void greet(String name, String greeting, int times) {
for (int i = 0; i < times; i++) {
System.out.printf("%s, %s\n", greeting, name);
}
}
void greet(String name, String greeting) { greet(name, greeting, 1); }
void greet(String name) { greet(name, "Hello", 1); }
Kotlin
fun greet(name: String, greeting: String = "Hello", times: Int = 1) {
repeat(times) { println("$greeting, $name!") }
}
As in Python and JavaScript
- Parameters can have default values
- String templates are built-in
Kotlin Koan: Named Arguments
Java
void createUser(String firstName,
String lastName, String email) { ... }
createUser("John", "Smith",
"john@example.com");
// or is it:
createUser("Smith", "John",
"john@example.com");
Kotlin
fun createUser(firstName: String,
lastName: String, email: String) { ... }
createUser(
firstName = "John",
lastName = "Smith",
email = "john@example.com")
// This also works:
createUser(
lastName = "Smith",
firstName = "John",
email = "john@example.com")
Named Arguments: joinToString
joinToString is declared with default parameter values:
fun joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
/* ... */
): String
Make joinOptions() return JSON format (e.g. [a, b, c]) using only two arguments:
fun joinOptions(options: Collection<String>) =
options.joinToString(TODO())
https://play.kotlinlang.org/koans/Introduction/Named%20arguments/Task.kt
Kotlin Koan: Default Arguments
Replace all these Java overloads:
public String foo(String name, int number, boolean toUpperCase) {
return (toUpperCase ? name.toUpperCase() : name) + number;
}
public String foo(String name, int number) {
return foo(name, number, false);
}
public String foo(String name, boolean toUpperCase) {
return foo(name, 42, toUpperCase);
}
public String foo(String name) {
return foo(name, 42);
}
with one Kotlin function so that useFoo() compiles:
fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)
https://play.kotlinlang.org/koans/Introduction/Default%20arguments/Task.kt
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
Kotlin Koan: Nullable Types
Rewrite this Java code so it only has one if:
public void sendMessageToClient(
@Nullable Client client,
@Nullable String message,
@NotNull Mailer mailer
) {
if (client == null || message == null) return;
PersonalInfo personalInfo = client.getPersonalInfo();
if (personalInfo == null) return;
String email = personalInfo.getEmail();
if (email == null) return;
mailer.sendMessage(email, message);
}
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
) {
TODO()
}
https://play.kotlinlang.org/koans/Introduction/Nullable%20types/Task.kt
when: Kotlin's Switch Expression
Java (pre-14)
String describe(int n) {
String result;
switch (n) {
case 0:
result = "zero";
break;
case 1:
case 2:
result = "small";
break;
default:
result = "other";
}
return (n < 0 ? "negative " : "")
+ result;
}
Java (14+, 2020)
String describe(int n) {
String result = switch (n) {
case 0 -> "zero";
case 1, 2 -> "small";
default -> "other";
};
return (n < 0 ? "negative " : "")
+ result;
}
String describe(int n) {
return (n < 0 ? "negative " : "")
+ switch (n) {
case 0 -> "zero";
case 1, 2 -> "small";
default -> "other";
};
}
Kotlin (2016)
fun describe(n: Int): String {
val result = when (n) {
0 -> "zero"
1, 2 -> "small"
else -> "other"
}
return (if (n < 0) "negative " else "")
+ result
}
fun describe(n: Int) =
(if (n < 0) "negative " else "") +
when (n) {
0 -> "zero"
1, 2 -> "small"
else -> "other"
}
L5: Before Records: 30 Lines for "I Want x and y"
public final class Point { // final = can't extend
private final int x; // final = immutable
private final int y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; } // accessors
public int y() { return y; }
@Override public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Point other)) return false;
return x == other.x && y == other.y;
}
@Override public int hashCode() { return Objects.hash(x, y); }
@Override public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }
}
All of this just to say: "I have an immutable point with x and y."
L5: Records (Java 16, 2021): Data Classes Without Boilerplate
A record is all 30 lines in one:
public record Point(int x, int y) {}
<p style={{fontSize: '0.85em', marginTop: '0.5em'}}>
You get automatically:
</p>
<ul style={{fontSize: '0.8em'}}>
<li>Constructor: <code>new Point(1, 2)</code></li>
<li>Accessors: <code>point.x()</code>, <code>point.y()</code></li>
<li>Correct <code>equals</code>, <code>hashCode</code>, <code>toString</code></li>
<li><strong>Immutability</strong>: all fields are <code>final</code>, class is <code>final</code></li>
</ul>
<aside className="notes">
Kotlin was introduced in 2011. Kotlin 1.0 came out in 2016.
→ Kotlin had it sooner -- and better.
</aside>
</Slide>
<Slide>
## Kotlin Data Classes
```kotlin
data class BankAccount(private val accountNumber: String, var balance: BigDecimal)
Kotlin data classes predate Java records and are more powerful.
| Java Record | Kotlin Data Class | |
|---|---|---|
| Mutability | Always final (immutable) | val (immutable) or var (mutable) |
| Field Visibility | Always private | Configurable (private, public, etc.) |
| Accessor Visibility | Always public | n/a |
A Mob Class
Java
public class Mob {
public enum Behavior {
Passive, Neutral, Hostile, Boss
}
public enum Status {
Healthy, Injured, Dead
}
private final String type;
private final int maxHearts;
private final Behavior behavior;
private int hearts;
public Mob(String type, int maxHearts,
Behavior behavior) {
this.type = type;
this.maxHearts = maxHearts;
this.behavior = behavior;
this.hearts = maxHearts;
}
public String getType() { return type; }
public int getMaxHearts() { return maxHearts; }
public Behavior getBehavior() { return behavior; }
public int getHearts() { return hearts; }
public Status getStatus() {
if (hearts == maxHearts) return Status.Healthy;
if (hearts == 0) return Status.Dead;
return Status.Injured;
}
@Override
public String toString() { return type; }
}
Kotlin
class Mob(val type: String, val maxHearts: Int, val behavior: Behavior) {
enum class Behavior {
Passive, Neutral, Hostile, Boss
}
enum class Status {
Healthy, Injured, Dead
}
override fun toString() = type
var hearts = maxHearts
private set
val status: Status
get() = when (hearts) {
maxHearts -> Status.Healthy
0 -> Status.Dead
else -> Status.Injured
}
fun main() {
val zombie = Mob("zombie", 20, Mob.Behavior.Hostile)
println("$zombie has ${zombie.hearts} hearts and is ${zombie.status}.")
println(zombie.takeDamage(2, 5))
println("$zombie has ${zombie.hearts} hearts and is ${zombie.status}.")
}
}
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()
}
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
Practice Using Smart Cast in When (if time)
// Describe a value using "when"
// Expected output:
// 42 is an Int
// hello is a String with 5 characters
// 3.14 is a Double
// true is something else
fun describe(value: Any): String =
when (value) {
TODO()
}
fun main() {
println(describe(42))
println(describe("hello"))
println(describe(3.14))
println(describe(true))
}
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
