Getty Images

Tip

Spring, Quarkus or Jakarta EE? How to choose a Java framework

Choosing a Java framework is not about which one is best, it's about accepting their tradeoffs of stability, flexibility and complexity. Here's how to evaluate each vs. your needs.

The first decision to kick off a greenfield Java project usually sounds breezy: "Let's start with Spring Boot, it's everywhere."

A few days in, someone mutters that Quarkus boots faster and saves memory. Then an old-school colleague asks why we abandoned Jakarta EE when its specs are battle-tested and vendor-supported. A DevOps voice warns about container bloat. An engineer waves the Java Platform Module System (JPMS) flag for compile-time hygiene.

Suddenly, the team is knee-deep in tradeoff talk.

Every option on the table -- Spring Boot, Quarkus, Jakarta EE, JPMS, Open Service Gateway Initiative (OSGi), or even handing the plumbing to an EIP toolkit such as Camel -- earns its keep by solving a real problem. Unfortunately, each one also drags in fresh baggage: incompatible APIs, upgrade headaches, steeper build pipelines, or operational overhead that won't go away until your service retires.

Factors to pick a Java framework

The task isn't to crown a single "best" architecture. It's to identify which pain your team can absorb without stalling delivery two years down the line.

  • Jakarta EE offers stability but is burdened by legacy and slow innovation.
  • Spring is flexible and powerful, but broad to the point of overwhelming.
  • Quarkus is fast and light, but its ecosystem is still catching up.
  • OSGi solves niche problems most teams don't have.
  • JPMS improves structure, but it isn't a framework.

All of them move complexity around, but none of them eliminate it.

With all that in mind, let's examine how to choose among various Java frameworks, each with their pros and cons.

Jakarta EE

Jakarta EE -- formerly Java EE and before that J2EE -- is generally regarded as the standard way to define enterprise services for the Java ecosystem. Its wide range of specifications includes dependency injection, resource management, web services, messaging, transactions and more.

Jakarta EE typically refers both to the specifications and the containers that implement them, such as WildFly and GlassFish. These application servers provide the environment into which developers deploy their code.

Over time, Jakarta EE has tried to adapt to newer development patterns, including those popularized by Spring. While it's more developer-friendly today, J2EE is still defined by its container model, and by the transition from the javax namespace to jakarta, a result of the trademark division between Oracle and the Eclipse Foundation. This namespace split has fractured compatibility and documentation, and undermined ecosystem consistency.

Summary: Jakarta EE is stable, mature and specification-driven, but at the cost of agility and modern library support.

Microservices: The hidden cost multiplier

Splitting a system into dozens of Spring or Quarkus microservices might sound elegant, until each JVM claims hundreds of megabytes of memory just to idle. Multiply that across dozens of services and you'll blow past your cloud budget or swamp your on-premises infrastructure.

Increased services require increased coordination, logging, TLS overhead and monitoring complexity. Quarkus helps somewhat with native builds, but even then each binary carries deployment and runtime costs.

A common rule of thumb: start with a modular monolith. Extract microservices only when the value exceeds the overhead.

If 90% of your code is passing messages, something like Apache Camel can offload the wiring. It works with Spring, Quarkus or Apache Karaf, and removes the need to write dozens of custom adapters. The downside: it's another DSL and requires reflection support for native images.

Spring

Spring was born as a rebellion against the heavyweight J2EE model. Rather than rely on a container for resource lookup, Spring gave developers tools for dependency injection, testability and lightweight configuration. Over time, Spring evolved into a full platform with Spring Boot, which lets you create self-contained, deployable applications with opinionated defaults and extensive integrations.

Spring has massively influenced how modern Java is developed. It's productive and powerful. But it's also sprawling, with often more than one way to do something, which creates confusion among development teams. It's also not immune to breaking changes. For example, Spring Boot 3 requires Java 17 and introduces behavior changes to Spring Security that can break older applications unless manually configured.

Summary: Spring is powerful, but with great flexibility comes complexity.

Quarkus

Quarkus is a newer alternative that emphasizes speed, low memory usage and native image support. Unlike Spring's runtime configuration, Quarkus leans heavily on build-time configuration, which leads to faster startup and smaller memory footprints. It's particularly attractive for Kubernetes and serverless environments.

Out of the box, Quarkus apps tend to use far less memory than comparable Spring apps. Developer experience is also a major focus, with hot reloads and fast feedback loops. However, the Quarkus ecosystem is smaller, and its limited support for reflection-heavy libraries can be a challenge when targeting native images.

Summary: Quarkus is fast and light, but its ecosystem lags other major Java frameworks.

OSGi: True modularity that few actually use

The OSGi was Java's answer to runtime modularity. Each bundle gets its own classloader to eliminate version clashes and enable hot swaps. In demos, it's amazing. In production, less so.

Challenges include the following:

  • A steep learning curve and minimal community support.
  • Limited ecosystem adoption outside of Eclipse and Karaf.
  • Complexity that often outweighs its modularity benefits.

Summary: Unless your project demands hot-swappable code, such as in industrial IoT, OSGi is more academic than practical.

JPMS: The compile-time seat belt

The JPMS is often confused as an application framework. It's not.

JPMS helps at compile time by enforcing module boundaries, hiding internal packages and reducing classpath issues. However, it doesn't help with dependency injection, dynamic loading or service orchestration.

Summary: JPMS is great for large, single-deployment monoliths that want to clean up internal structure. It's not a drop-in replacement for Spring or Quarkus. Use it for hygiene, not architecture.

Putting it all together

Picking a Java framework isn't about finding the "best" one. It's about understanding the tradeoff and choosing what your team can live with.

The key to your greenfield Java project's long-term success is to document your architectural bets, define the pain you'll tolerate and keep things modular enough that you can pivot as your needs evolve.

Joseph B. Ottinger has held senior roles in software engineering and project management. He's written countless articles and multiple books on various languages, architectures and implementations, including Hibernate and Spring.

Dig Deeper on Front-end, back-end and middle-tier frameworks