4 min read
Why I Moved From Java to Kotlin for Android Apps

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.