I’ve been writing Android apps in Java since university and through my early work at iLab Africa. This year I made the switch to Kotlin for new projects, and after a few months with it in production I have enough opinions to write something useful. Google made Kotlin a first-class language at I/O last year - I held off until I had real experience to share, not just enthusiasm.
The specific pain points I was solving: verbose code, boilerplate, and a steady stream of NullPointerException crashes.
1. Data Models: POJO Boilerplate vs Data Classes
Java POJO (~50 lines)
public class Task {
private String id;
private String title;
private boolean completed;
private long updatedAt;
public Task(String id, String title, boolean completed, long updatedAt) {
this.id = id;
this.title = title;
this.completed = completed;
this.updatedAt = updatedAt;
}
public String getId() { return id; }
public String getTitle() { return title; }
public boolean isCompleted() { return completed; }
public long getUpdatedAt() { return updatedAt; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
Task task = (Task) o;
if (completed != task.completed) return false;
if (updatedAt != task.updatedAt) return false;
if (id != null ? !id.equals(task.id) : task.id != null) return false;
return title != null ? title.equals(task.title) : task.title == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (title != null ? title.hashCode() : 0);
result = 31 * result + (completed ? 1 : 0);
result = 31 * result + (int) (updatedAt ^ (updatedAt >>> 32));
return result;
}
@Override
public String toString() {
return "Task{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", completed=" + completed +
", updatedAt=" + updatedAt +
'}';
}
}
Kotlin data class (1 line)
data class Task(
val id: String,
val title: String,
val completed: Boolean,
val updatedAt: Long
)
Kotlin generates equals, hashCode, toString, and copy automatically. That’s the whole model.
2. Null Safety: Defensive Java vs Compiler-Enforced Kotlin
Java: Manual Checks
Java requires defensive code to avoid crashes.
public String getTaskTitleSafe(Task task) {
if (task == null || task.getTitle() == null) {
return "";
}
return task.getTitle();
}
Kotlin: Type System Safety
Kotlin includes nullability in the type system.
fun getTaskTitle(task: Task?): String {
return task?.title ?: ""
}
The safe-call ?. and Elvis operator ?: eliminate most NPEs. I’ve had zero null pointer crashes in the Kotlin projects I’ve shipped this year - versus a regular trickle in the Java ones.
3. Utilities: Static Methods vs Extension Functions
Java: Static Utility Classes
public final class TaskUtils {
private TaskUtils() {}
public static boolean isRecentlyUpdated(Task task, long thresholdMillis) {
long now = System.currentTimeMillis();
return now - task.getUpdatedAt() <= thresholdMillis;
}
}
// Usage: TaskUtils.isRecentlyUpdated(task, 60_000);
Kotlin: Extension Functions
fun Task.isRecentlyUpdated(thresholdMillis: Long = 60_000L): Boolean {
val now = System.currentTimeMillis()
return now - updatedAt <= thresholdMillis
}
// Usage: task.isRecentlyUpdated()
Migrating a Real App
I migrated an existing production app using a module-by-module approach, starting with utility classes and data models. Android Studio’s Java-to-Kotlin converter gives you a rough draft but the output needs manual cleanup to be idiomatic. Mixed Java and Kotlin projects work fine - both compile to the same bytecode, so you’re not forced to migrate everything at once.
Conclusion
Kotlin is clearly where Android development is heading. For a small team the benefits are tangible: fewer crashes, less boilerplate to read and review, and a type system that catches whole categories of bugs at compile time. I still have Java code running in production and I’m not ripping it out, but all new work starts in Kotlin.