Why I chose Micronaut over Spring Boot (and Kotlin along the way)
When I set out to build the backend for a product that replaced a set of legacy reports with one modern application, the framework choice mattered more than it usually does. This was not a typical CRUD service. It was a data-heavy product, combining real-time and batch data, that had to serve a large user base with summaries in milliseconds and scale horizontally without the cost getting out of hand. The obvious default on the JVM is Spring Boot. I went a different way, and I want to explain why.
What the service actually had to do
Before naming any framework, I wrote down what the API layer needed:
- Low, predictable latency. The database side was fast, so the service in front of it could not become the bottleneck.
- Fast startup and a small memory footprint. In a containerized, autoscaling world, instances come and go constantly. A service that takes many seconds to start and holds a large heap is expensive and slow to scale.
- High concurrency. The product aimed at tens of thousands of users, so the request path had to stay efficient under load.
- A clean developer experience. The team needed to move quickly without fighting the framework.
Those requirements did most of the deciding for me.
Where Spring Boot gave me pause
Spring Boot is excellent, and I want to be fair about that. The ecosystem is enormous, the documentation is everywhere, and for most services it is a completely reasonable default. My hesitation was specific to this workload.
Spring’s core wires the application together largely at runtime. Dependency injection, proxies, and much of the autoconfiguration rely on reflection and classpath scanning as the app boots. That flexibility has a cost: slower startup and a larger memory footprint. For a long-running monolith that starts once, you rarely notice. For a service that scales out and in constantly and where cold start time is real money, that cost becomes part of the architecture.
Why Micronaut fit
Micronaut inverts the expensive part. It does its dependency injection and aspect-oriented wiring at compile time rather than at runtime. The framework processes annotations during the build and generates the wiring ahead of time, so at startup there is little to no reflection or classpath scanning left to do.
That single design choice lines up with everything I needed:
- Fast startup. Because the wiring is already done, instances come up quickly, which makes autoscaling responsive instead of sluggish.
- Low memory footprint. Less runtime machinery means smaller heaps, so I could run more per node and keep costs down.
- Non-blocking by default. Micronaut is built on a reactive, Netty-based HTTP stack, which suited a high-concurrency, low-latency request path.
- Ahead-of-time and native friendly. The compile-time model also plays well with GraalVM native images, which keeps the door open for even leaner deployments later.
None of this makes Spring Boot wrong. It makes Micronaut a better match for a service whose whole job is to be fast, cheap to scale, and light on its feet.
Why Kotlin
Choosing Micronaut opened the door to Kotlin, and I took it. A few reasons stood out:
- Null safety. Kotlin’s type system pushes null handling to compile time, which removes a whole category of runtime failures from a service that has to stay up.
- Conciseness. Data classes, default arguments, and less ceremony mean less boilerplate and code that is easier to read and review.
- Coroutines. Structured, readable asynchronous code fit naturally with a non-blocking framework, without drowning in callbacks.
- Full JVM interoperability. Kotlin runs on the same JVM and uses the same libraries, so nothing about the ecosystem was off limits.
Micronaut has first-class Kotlin support, so the two worked together cleanly rather than fighting each other.
The honest tradeoffs
This was the right call for this workload, but it was not free. Spring Boot has a larger community, more tutorials, and more engineers who already know it, so hiring and onboarding are easier. Some libraries are Spring-first, and you occasionally do more integration work on a smaller framework. If I were building a standard service where startup time and footprint did not matter, I would happily reach for Spring Boot.
The takeaway
The lesson I keep coming back to is that framework choice should follow the workload, not habit. For a latency-sensitive, cost-sensitive product that scales horizontally, Micronaut’s compile-time approach and Kotlin’s safety and brevity were a genuinely better fit than the default. Write your real constraints down first, and more often than not they will pick the tool for you.