u/spokimono

▲ 0 r/Kotlin

We've spent years fixing JVM startup. What if that's the wrong problem?

Our prod backend — Spring + Hibernate — had a ~25s cold start, on a system that scales out during traffic spikes. So a slow boot wasn't slow, it was user-visible downtime. In an R&D spike I built the same service on Kotlin Multiplatform: ~3s cold start on the JVM, under 0.3s compiled to Kotlin/Native — same code, two targets. That gap is why I think the whole JVM-startup arms race — CRaC, Leyden, GraalVM — is solving the wrong problem. I hit a wall worth arguing about.

To kill the obvious misread: I did not port that prod backend to native — it runs on the JVM and always will. The KMP build was a separate spike; it matured into something we could have shipped, then the company shrank and it never ran in prod.

We didn't skip the JVM's own startup playbook — GraalVM got the most serious look, and it's good tech that just didn't fit us: closed-world reflection config (even Ktor needs the CIO engine plus a JSON file from the tracing agent), AOT trading away the JIT on a workload that stays warm long enough to want it, heavy CI.

The main point. We've decided the fix for "the VM starts slowly" is an arms race to make the VM start fast. At some point it starts to feel like we'd rather rebuild the VM as a native compiler than admit the VM itself is the startup tax. Native doesn't pay that tax in the first place — that 0.3s boot, one binary, tiny footprint, nothing to install on the host. And 3s isn't "fine" here. When you scale to zero and back on a spike, cold start is on the request path, not hidden in a startup log. Even a tuned 3s is latency a user eats; 0.3s makes it disappear. That's not a tuning win — it removes the problem.

Then the wall: no JDBC on Native — and there can't be, JDBC is a JVM standard, which means no mature ORM on top of it either. Grown-up native ecosystems solved this by reimplementing the Postgres wire protocol in their own language: Go has pgx, Rust has sqlx / tokio-postgres, no libpq. Kotlin/Native has almost none of that, so I bound libpq through cinterop to get something working. That was where I stopped looking for an ORM and accidentally started writing one. I kept solving the next missing piece until I had a full data layer. That project eventually became Kormium. I'll leave the link in the comments for anyone who's curious.

Concurrency is where I expect hits. On the JVM the answer is Loom: block on a JDBC call, the carrier thread parks cheaply, your ceiling is the connection pool. Native has no Loom, but libpq has an async API, so you can drive it off a poll-based socket reactor and get genuinely non-blocking suspend with no VM underneath.

No flag-waving: I'm not claiming native is faster. Rust handles load better, a warm JIT beats AOT on sustained throughput. Native wins on startup, footprint, and not shipping a VM. K/N's realistic ceiling is Go's niche, not Rust's.

Here's the take I want proven wrong: Kotlin/Native isn't adopted on the backend because backend performance still isn't compelling enough → nobody builds the ecosystem (the missing data layer is the hole I fell into) → no pressure on JetBrains → performance doesn't improve → nobody adopts it. The root is performance, the rest is symptoms.

So — tell me where I'm wrong. Do CRaC/GraalVM genuinely close the gap, making native on the server unnecessary? Or is the direction right and JetBrains just digging in the wrong place? And if you've run K/N (or Go/Rust) on a backend, tell me where it broke — that's the data we're all missing.

reddit.com
u/spokimono — 6 days ago