Someone asked me, a few months into building Resulign, why I wasn't using something lighter. Node, Go, Bun — something that starts faster, has less ceremony, doesn't require knowing what a BeanDefinitionOverrideException is at midnight.
It's a fair question. I've seen the Spring Boot defense go two ways: someone lists enterprise features, or they hedge with "it depends." Neither is useful.
What "production-ready" means when you're the only one on call
The phrase "production-ready" gets attached to almost every framework now. What it actually means when you're a solo operator is unglamorous and specific.
For me, it means: when something breaks at 2am, how long until I understand what happened?
Spring Boot Actuator gives me health endpoints and metrics with no configuration. Flyway ties database state to the deployment — a failed migration stops the process before I run against the wrong schema. Spring Security's defaults don't require me to have a complete threat model before I ship; I can tighten the configuration as I learn the attack surface.
These aren't features I evaluated on a checklist. They're things I've needed at specific moments when I had no time to build them from scratch.
If I were on Express, I'd be assembling that infrastructure manually. Possible, but the cost accumulates — across every library update, every new service, every environment you add. Spring Boot's defaults work together because they were designed to. That matters differently when you're the only engineer.
The ecosystem bet
This one is forward-looking, and I want to be clear I'm making a bet, not stating a fact.
Java 21 virtual threads (Project Loom) change the concurrency model enough that the "Go for concurrency" argument weakens for a lot of I/O-bound workloads. You get blocking-style code with async performance characteristics. For a backend that's mostly calling external APIs, querying a database, or reading from a queue — this matters.
Spring AI is still early, but it uses the same dependency injection, configuration, and bean model as the rest of the stack — no separate service boundary needed just to call an LLM. That coherence is part of why the bet feels reasonable.
My bet is that the Java and Spring ecosystem keeps maturing in a direction that's useful for a production SaaS, and that the upfront cost compounds into reliability over time. That bet could be wrong. I'm aware of it.
What I actually gave up
Startup time is the obvious one. Spring Boot on a cold JVM is slower than a Go binary or a small Node service. For long-running services on a managed instance, this is close to irrelevant. For serverless or heavily scaled environments where cold starts happen constantly, it's a real cost.
Memory footprint is real. A Spring Boot application at idle uses more memory than a comparable Node or Go service. At Resulign's current scale this hasn't been a constraint, but I wouldn't pretend the number is small.
Onboarding friction is the most honest tradeoff. If I ever hire a developer who doesn't know Java, the ramp to being productive in this codebase is longer than it would be on TypeScript. I'm deferring that cost. I'm betting I'll either hire Java engineers or that familiarity on my end outweighs the ramp cost for someone new — but that's a bet, not a guarantee.
I want to be honest about all three because the tradeoffs are real and anyone who tells you otherwise is selling something.
When Spring Boot is the wrong choice
If you're building a small internal tool with a three-month runway, Spring Boot is probably too much. The startup configuration takes time that a Python Flask app or a Node Express service doesn't require.
If your team is entirely JavaScript engineers who have never touched Java, you're adding a language switch on top of a framework switch. The productivity cost in the short term is real and may not be worth it regardless of what the framework offers.
If you need sub-100ms cold starts consistently — certain serverless architectures, edge functions — Spring Boot's JVM startup is a genuine limitation. Spring Native (GraalVM) partially addresses it but doesn't fully solve it.
The cases where I'd tell someone to go with something else are real. The framework doesn't become right just because it's my choice.
The actual answer
I'm on Spring Boot because one person needs to keep this backend running for years. A typed system catches whole categories of mistakes before they ship. The production tooling — Actuator, Flyway, the Security defaults — exists and works without me assembling it from parts. That combination reduces what I have to hold in my head at once, which matters when there's no one else holding the rest.
It's also worth saying: I know this stack. There's a version of "use the best tool" that ignores how much knowing a tool changes what you can build with it. I can debug a Spring Data query issue in minutes. A Go ORM issue might take me hours — not because Go is worse, but because I'd be debugging from documentation rather than from experience.
The AI failure modes I wrote about recently are partly stack-dependent — the proxy model, the annotation semantics, the session lifecycle are Spring-specific. Understanding them is part of what makes the stack productive for me. Someone who doesn't know Spring Boot's internals will pay a higher review cost on AI-generated code, and that compounds.
Where this leaves me
The cases where Spring Boot is the wrong choice are real — I listed them above. The "it's too heavy" argument usually conflates startup configuration with production behavior. Spring Boot has a setup cost. That cost buys you a production system that works without ongoing assembly. Whether the tradeoff makes sense depends on what you're building and how long you plan to be the one maintaining it.
That's the whole answer.
If you're working through similar stack decisions as a solo builder, I write about this kind of thing on LinkedIn and X.