Switching from Java to Kotlin: My Honest Reflection After One Year

About a year ago, I made the switch from Java to Kotlin. Not because I was chasing hype or I am obsessed with cutting-edge languages (Well... I am). My new job required it. I had played around with Kotlin before, even liked some of its features, but using it in a real production environment, especially in a microservice setup with Spring Boot, was a whole different story.
This post is for people like me: developers who have worked with Java for years, who know its quirks and strengths, and who are now wondering whether Kotlin is really worth the effort. I’m not going to convince you one way or another. What I will do is share what I ran into, what I had to unlearn, and where Kotlin has actually made my life easier.
Leaving my Comfort Zone
Before Kotlin, my Java setup was clean and efficient. I followed Clean Architecture principles, relied on Lombok to eliminate boilerplate, and used MapStruct extensively to map between layers. Streams and Optionals were part of my everyday toolbox, and the structure felt solid.
Switching to Kotlin wasn’t a leap into the unknown. I had read up on it, played around in side projects, and I knew about things like classes being final
by default. I was prepared, at least I thought I was.
But the real friction came from the day-to-day realities. Things that felt natural in Java suddenly became roadblocks:
- Trying to mock a final class with Mockito and getting weird runtime errors – until I switched to MockK.
- Running a Spring Boot app and wondering why nothing works – because I forgot to apply the
kotlin-spring
plugin to make my beans open. - JPA entities throwing errors because Kotlin's
val
fields have no setters and no default constructor – forcing me to either rewrite or reach for thekotlin-jpa
plugin. - Writing DTOs and wondering why Jackson freaks out when deserializing a missing field – only to realize that non-null Kotlin properties without defaults will crash hard unless you configure Jackson just right.
- Expecting MapStruct to "just work" but running into KAPT configuration issues and lack of support for Kotlin idioms.
These weren’t bugs. They were signs that Kotlin demands a different mindset and doesn’t always play perfectly with Java-era tooling. None of it was a dealbreaker, but every one of them was a speed bump. And they all added up.
Learning to Think Differently
Kotlin isn’t hard to pick up but it is hard to unlearn Java.
I loved Java Streams. Fluent chaining was second nature. In Kotlin, I had to adjust to let
, run
, apply
, also
, takeIf
, and friends. It’s powerful, but at first it felt inconsistent and a bit magical. Sometimes, reading my own code was harder than it needed to be.
Null-safety is a big one. Kotlin forces you to be explicit. That’s a good thing and I’ve had far fewer null-related bugs. But with things like Jackson, you still have to be careful. If a JSON field is missing or null
, and your Kotlin field is non-null without a default, Jackson will throw an exception unless configured properly. The jackson-module-kotlin
helps a lot, and default values in data classes are your friend. Still, the first few crashes were confusing.
Build Performance: Better Than Expected
I’d read that Kotlin compiles slower than Java and that was true back in the 1.x days. But with Kotlin 2.1 and the new K2 compiler? Things are much faster now.
Even with annotation processing, builds are way more predictable. Incremental builds are snappy, and clean builds don’t feel painful anymore. KAPT still adds some overhead, but nowhere near what it used to.
And if you're using libraries that support KSP (Kotlin Symbol Processing), you can ditch KAPT entirely.
Overall: build performance isn’t a blocker anymore
Testing and Mocking: Some Tweaks Needed
I ran into some mocking issues early on. Kotlin classes are final by default, so Mockito didn’t work as expected unless I enabled the inline mock maker. That helped, but eventually I switched to MockK.
MockK is made for Kotlin. It mocks val
, top-level functions, suspend functions and all the weird Kotlin things that Mockito struggles with. It took a bit of adjustment, but now I prefer it.
My test code got cleaner too. Data classes make test data easy to set up, and Kotlin’s syntax helps keep things readable.
Interop with Java: Mostly Smooth, Occasionally Bumpy
Mixing Java and Kotlin in the same project? Totally doable. But there are edge cases:
- Java libraries without nullability annotations make Kotlin guess and that’s not always safe.
- Some frameworks rely on reflection or proxies in ways that clash with Kotlin’s defaults.
- Hibernate and Jackson both require special attention. Thankfully, Spring Boot configures a lot of this for you if you include the right modules.
It’s not broken but it’s not magic either. You have to understand what Kotlin expects, and what your Java libraries are doing under the hood.
Mapping: The Long Goodbye to MapStruct?
I used to rely heavily on MapStruct. In Java, it was fast, clean, and handled everything. In Kotlin, it technically still works but it needs KAPT, and mapping into val
-only data classes gets tricky.
I tried alternatives like ShapeShift. Nothing really clicked. Eventually, I gave up and started writing mappers by hand. Not ideal but manageable.
That said, MapStruct 1.6 has improved Kotlin support, and with the recent compiler improvements, even KAPT-based mapping is tolerable again. But I still miss the old days of @Mapper
and being done with it.
Coroutines: Async Magic I Haven’t Mastered Yet
If there’s one Kotlin feature everyone raves about, it’s coroutines. And honestly? I haven’t fully jumped in yet.
I understand the concept: lightweight threads, structured concurrency, clean async code. I’ve seen the examples, read the docs, and even used a few suspend
functions here and there. But in my current projects, I haven’t had a strong use case that forced me to go deep. And to be totally honest, they still feel a bit intimidating.
The different coroutine builders (launch
, async
, runBlocking
), the context propagation, the lifecycle handling – it’s powerful, no doubt. But it’s also a different way of thinking compared to the traditional request/response blocking model.
I know Spring Boot 3 supports suspend
functions now, and that makes the entry point much easier. I plan to take advantage of that. For now though, it’s still on my learning list. One of those things where I know the payoff is there, I just haven’t reached for it yet.
What I Gained – and What I Gave Up
Gained:
- Fewer bugs from null issues
- Less boilerplate
- More expressive code
- Cleaner async handling
- A new way of thinking about code
Gave up:
- Some of my favorite tools (Lombok, MapStruct)
- A bit of initial productivity
- The comfort of “I know exactly how to do this in Java”
Should You Switch?
It depends. If you’re starting fresh, or if you’re in a place where you can explore, yes, try Kotlin. You’ll probably like it. It’ll challenge your assumptions and make you think differently.
If you have a massive legacy Java codebase that works fine? Maybe not. Kotlin isn’t a magic fix. You’ll have friction. Especially early on.
For me? I’m glad I had to switch. It forced me out of my comfort zone, made me a better developer, and gave me a more modern toolbox.