Giving Kotlin a Real Try Over a Weekend
I’d been circling Kotlin for a while without ever committing to it, so I finally blocked out a weekend to rewrite a small personal project in it instead of just reading about it.
Why I kept putting it off
Java has been my daily driver for years, and “Kotlin is basically nicer Java” is the kind of pitch that’s easy to nod along to and never act on. Syntax sugar isn’t a good enough reason to block out a weekend. What actually got me moving was null safety: I was tired of writing defensive if (x != null) checks out of habit rather than necessity, and Kotlin bakes that directly into the type system instead of leaving it as a convention.
The project
I picked something small and self-contained: a command-line tool that watches a folder and rewrites filenames into a consistent format (lowercase, hyphenated, no special characters). Nothing fancy, but real enough to touch files, handle errors, and need tests.
fun normalizeName(name: String): String =
name.lowercase()
.replace(Regex("[^a-z0-9.]+"), "-")
.trim('-')
fun main(args: Array<String>) {
val dir = File(args.getOrElse(0) { "." })
dir.listFiles()?.forEach { file ->
val newName = normalizeName(file.name)
if (newName != file.name) file.renameTo(File(file.parent, newName))
}
}
What actually felt different
Data classes were the first thing that made me laugh out loud at how much Java boilerplate I’d been writing for years:
data class RenameResult(val original: String, val renamed: String, val success: Boolean)
That’s a constructor, equals, hashCode, toString, and copy() in one line. The Java equivalent is twenty-plus lines I’ve written more times than I’d like to admit.
The other thing that stuck was extension functions. Being able to add a method to String or File without subclassing or wrapping it in a utility class changes how you reach for small helpers. I ended up with a File.isHidden(): Boolean extension that read more naturally at every call site than a static utility method would have.
Where it got friction-y
Interop with existing Java libraries is seamless almost all the time, except when it isn’t. I hit one spot where a Java library’s nullable return type wasn’t annotated, so Kotlin treated it as a “platform type” and happily let me ignore a null check I actually needed. It compiled fine and blew up at runtime exactly where Kotlin was supposed to save me from that.
Build times were also noticeably slower than the equivalent Java project, which surprised me for something this small. Not a dealbreaker, just a thing I didn’t expect going in.
Where I landed
I’m not rewriting everything in Kotlin overnight, but it’s earned a permanent spot in the rotation for new personal projects, especially anything with real domain modeling where data classes and sealed classes pull their weight. The null safety alone is worth the switch for new code; I just wish I’d tried it for a real project instead of toy examples years ago.