For years, Java development has often meant reaching for a framework or library the moment a new need appears. That's been rational, and I've done the same. But the Java platform has grown, the language has gotten better, and the cost of writing mechanical code by hand has dropped. So I've started turning over a question I don't have a finished answer to: how much of what we usually add do we still actually need?
01 · The idea
This isn't about Spring being the problem
Spring Boot solves a lot of hard problems, and it solves them well. For public APIs, security, advanced routing, observability, and large integration surfaces, Spring is probably still the cheapest option overall.
But Spring has become such a default reach that I suspect we sometimes grab it even when the problem is smaller than the solution. That's the suspicion I want to examine here.
A small internal service might need only a handful of HTTP calls, a database, one outgoing call, and some business logic. Java 25 already has an HTTP client, JDBC, scheduling, records, logging, JFR/JMX, and modern concurrency tools. Virtual threads also make plain, blocking code considerably more attractive for I/O than it was a few years ago.
02 · The maintenance surface
Maybe dependencies are a bit of an iceberg
When we look at a pom.xml or build.gradle, we only see part of the system's technical surface. Underneath it sit transitive libraries, version couplings, autoconfiguration, proxies, annotation processing, and conventions that someone has to understand the day something breaks.
Counting JAR files therefore feels like a blunt measure. A small external dependency can be very cheap. Four hundred lines of hand-rolled OAuth code can be very expensive.
So by minimalism I don't mean the least code, or zero libraries. Rather, less unnecessary semantics – less technology the team has to carry over time.

03 · A proposed order
What if we asked in this order?
Instead of starting with "which library should replace this?", you could work through four steps. The further right we get, the more specialized semantics we choose to let someone else own. The order isn't a truth – mostly a way to make the choice deliberate.
04 · Where Spring might fit best
What if Spring lived at the edge?
The idea I personally find most interesting isn't about removing Spring Boot. It's about maybe no longer letting Spring be the application's primary programming model.
My thinking is that if the core is free of Spring, the next decision becomes less dramatic. A complex service can happily stay on trimmed-down Boot. A small internal service could later move further toward plain JDK. A batch job or worker might never have needed Spring in the first place. But that's just a thought – I don't know how well it holds up in your systems.
05 · A few things I'm curious about
Not everything probably needs the same treatment
Lombok and mappers
Records and explicit code mean some of the mechanical help feels less valuable than it used to.
Outbound HTTP
The JDK HttpClient seems to go a long way for ordinary service-to-service communication.
Spring DI
Constructor injection needs no container. At the same time, a large and dynamic object graph can very well justify Spring.
JPA / Hibernate
For clear SQL and a limited model, JDBC can be simpler. For rich object graphs, an ORM is probably still cheaper.
JSON and security
General-purpose JSON parsing and OAuth/OIDC carry difficult semantics that should rarely become in-house infrastructure.
Connection pool
JDBC comes with the JDK, but a production-grade pool doesn't. HikariCP feels like an obvious dependency to keep.
06 · What would we gain?
If it holds, it's about maintenance cost
What would make this idea worth anything isn't a tidier dependency tree. It's whether, over several years, the team ends up with less technology to coordinate, fewer layers to understand, and less impact when the platform is upgraded. And that's of course an assumption that needs to be tested.
| If we reduce… | We might gain… | But only if… |
|---|---|---|
| External dependency families | Fewer separate version and upgrade tracks | We don't replace them with large in-house frameworks |
| Spring types in the domain | Less impact from framework upgrades and simpler tests | The boundaries between core and adapters stay clear |
| Runtime magic | More visible control flow and easier debugging | The explicit code stays small and understandable |
| Excessive standardization in code | Less local infrastructure | The organization still standardizes important contracts and operational behavior |
07 · One thing that may have changed
More code can sometimes mean a smaller system
AI makes it cheaper to produce mechanical mappers, JDBC row mappers, config records, adapters, tests, and repetitive decorators. If that's true, it lowers the value of abstractions whose main contribution is that we get to skip writing a few lines.
At the same time, AI hardly makes protocols, cryptography, parser edge cases, concurrency, or transaction semantics any less dangerous. It's quick to generate a connection pool. That doesn't mean the team should own one.
08 · Where I've landed so far
Maybe minimalism is mostly about choosing responsibility
I don't see the point in making "zero Spring" a creed. But I do think there's something to making every dependency a deliberate choice.
A small service might get by on the JDK's HTTP server. A complex public-facing service can happily stay on WebMVC and Spring Security. A worker might end up being nearly plain Java. What they'd have in common is that the business logic gets to be ordinary Java, and specialist libraries are kept at clear boundaries.
