Quarkus vs. Spring Boot

This section documents Quarkus 3.39.x (current) with 3.33.x LTS as the production recommendation, on the Java 17+ baseline, as published at the official Quarkus guides — which are the reference these pages are written and verified against.

Quarkus 4 raises the baseline to Java 21 (first release candidate planned for end of September 2026, see the Quarkus blog); readers upgrading past 3.39.x/3.33.x LTS should expect that change.

This content was generated with the assistance of AI and should be verified against those official guides before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Quarkus and Spring Boot solve the same problem — building and running a production Java backend service — from two different starting assumptions about when the framework is allowed to do its work. Spring Boot optimises for the broadest possible ecosystem and a stable, well-documented runtime programming model: components are discovered, configured and wired together while the application is running, which is what makes Spring’s vast library ecosystem, its dynamic proxying, and its runtime-conditional auto-configuration all work the way they do. Quarkus optimises for startup time, memory footprint and container density by moving as much of that work as possible to build time, ahead of running anything, at the cost of a closed-world assumption: the set of beans, endpoints, and reflection targets an application will ever use must be knowable before the JVM (or native-image) ever starts. Neither optimization target is wrong; they are different bets about where the cost of building a service should be paid — once, at build time, or repeatedly, at every process start.

Concept and annotation mapping

The table below maps the Spring Boot idiom most Java developers already know to its closest Quarkus/Jakarta/ MicroProfile equivalent. Treat "equivalent" loosely — several rows below are also covered, from the Quarkus side, on Spring API Compatibility Extensions, which documents exactly how far the mechanical translation goes and where it stops.

Spring Boot Quarkus Notes

@SpringBootApplication

no equivalent — nothing to bootstrap

Spring Boot’s single annotation both marks the application class and triggers component scanning and auto-configuration at startup. Quarkus has no comparable bootstrap annotation because there is no runtime scanning step to trigger: the equivalent work (discovering beans, resolving config, wiring routes) already ran during build-time augmentation, so the generated main class simply replays what augmentation already decided.

@Component / @Service / @Repository

@ApplicationScoped (or another CDI scope)

Mechanical in the common case, but CDI has no single "just a bean" stereotype the way Spring’s component scanning does — picking a scope (@ApplicationScoped, @RequestScoped, @Singleton) is a small, deliberate step. Covered on CDI and the Programming Model (ArC); compare Spring’s own stereotypes on Core Spring & Spring Boot Annotations.

@Autowired

@Inject

Direct rename in the field/constructor-injection case; see CDI and the Programming Model (ArC).

@Value / @ConfigurationProperties

@ConfigProperty / @ConfigMapping

@ConfigProperty matches @Value’s single-property injection; a `@ConfigMapping interface is the type-safe, structured equivalent of @ConfigurationProperties, resolved and validated at build time rather than bound reflectively at startup. See Configuration and Configuration and Profiles.

@RestController + @GetMapping/etc.

@Path + @GET/@POST/etc.

Mechanical for a plain CRUD-style resource; a controller relying on Spring MVC’s HandlerInterceptor chain or content-negotiation configuration needs a rewrite against Quarkus REST's own extension points, covered alongside Spring’s model on REST APIs with Spring MVC and WebFlux.

RestClient / @HttpExchange

@RegisterRestClient

Both declare a typed HTTP client from an interface. @RegisterRestClient is the MicroProfile REST Client — a standard, not a Quarkus invention —  with CDI injection and pluggable JSON providers; Spring’s declarative equivalents are covered on REST APIs with Spring MVC and WebFlux.

Spring Data repositories

Panache / Quarkus Data

extends JpaRepository<T, ID>-style derived queries map to Panache’s repository pattern or Quarkus Data; Panache additionally offers an Active Record style with no direct Spring Data equivalent. See Spring Data Overview for the Spring side.

@Transactional (org.springframework.transaction.annotation)

@Transactional (jakarta.transaction)

Same annotation name, different package and a different transaction manager underneath (Narayana JTA rather than Spring’s PlatformTransactionManager) — an import change more than a logic change for ordinary propagation cases. See Datasources and Transactions.

@Cacheable / @CacheEvict

@CacheResult / @CacheInvalidate

Same shape, different annotation names and package. See The Quarkus Cache Extension and Caching: the Spring Cache Abstraction, Caffeine, and Redis.

@Scheduled

@Scheduled (io.quarkus.scheduler.Scheduled)

The annotation name is identical but the attributes are not: Spring’s fixedRate/fixedDelay become Quarkus’s every and delay/delayed; cron carries over directly. See Scheduling and Mail and Scheduling and ShedLock.

Reactor Mono/Flux

Mutiny Uni/Multi

Both model, respectively, a single deferred value and a stream of values, but the operator vocabulary and subscription semantics differ enough that porting code is a rewrite, not a search-and-replace. See Reactive Programming and Mutiny and Reactive Programming with Project Reactor.

Actuator

/q/* endpoints + SmallRye Health/Micrometer

Spring Boot Actuator bundles operational endpoints under one dependency and one path prefix. Quarkus splits the same concerns across extensions surfaced under /q/: SmallRye Health for /q/health, Micrometer (the same library Spring Boot uses) for /q/metrics, and the Dev UI at /q/dev in dev mode.

Testcontainers wired by hand

Dev Services

Both give a real containerized dependency for local/test runs. Testcontainers under Spring Boot is typically a @Container field and lifecycle code the developer writes; Quarkus’s Dev Services start (and stop) the matching container automatically, with zero configuration, the moment an unconfigured extension needs one.

@SpringBootTest

@QuarkusTest

Both boot the full application context/container in the test JVM. See Testing Quarkus Applications and Unit and Integration Testing.

Spring profiles

Quarkus profiles

Conceptually identical — a named configuration variant selected at startup (%dev, %test, %prod built in, custom profiles supported by both). See the "Profiles" section of Configuration and Configuration and Profiles.

Spring Cloud

the Quarkus/SmallRye equivalents

There is no single Quarkus package matching Spring Cloud’s breadth. Individual concerns map to individual extensions instead: Fault Tolerance and Resilience (SmallRye Fault Tolerance) for what Spring Cloud Circuit Breaker/Resilience4j cover (see Resilience4j with Spring Boot), Stork (covered alongside the REST Client) for client-side service discovery, and Kubernetes and OpenShift plus the spring-cloud-config-client compatibility extension for centralized configuration — see Configuration and Profiles for the Spring Cloud Config side of that last one.

Diagram mapping common Spring annotations on the left — @Autowired

Architecture: runtime reflection and a runtime container vs. build-time augmentation and a closed world

Spring Boot’s container is a genuinely dynamic, general-purpose object graph: an ApplicationContext built at runtime from whatever beans component scanning finds and whatever auto-configuration classes decide their @Conditional`s are satisfied, using reflection and (for most bean definitions) CGLIB/JDK dynamic proxies to apply cross-cutting behavior. That generality is precisely what lets an arbitrary third-party Spring library register its own `BeanFactoryPostProcessor, react to ApplicationContext events, or introduce a new @Conditional — the container has no fixed idea, ahead of time, of what it will contain, which is also why the same container has to redo that discovery-and-wiring work on every process start.

Quarkus makes the opposite trade: build-time augmentation requires a closed-world assumption — the full set of CDI beans, configuration properties, REST endpoints and reflection targets an application will ever need must be knowable at build time — and in exchange resolves essentially all of that work once, emitting bytecode that mostly just replays a known-good outcome at every start. The cost of that trade lands on the library side rather than the application side: a library that resolves its own behavior through unconstrained runtime reflection, dynamic classloading, or a dynamically generated proxy the closed-world analysis cannot see through either does not work unmodified on Quarkus, or needs a Quarkus extension purpose-built to teach the build-time analysis about it. That is exactly the gap the Spring API compatibility extensions paper over for a fixed, documented set of Spring annotations — and exactly why they stop there rather than reimplementing Spring’s own runtime container.

Startup, memory, and native image

On the JVM, Quarkus’s build-time-resolved startup path means the only work left at process start is runtime init — reading real configuration, opening sockets and connection pools — rather than classpath scanning and auto-configuration evaluation, which is the mechanical reason Quarkus JVM-mode startup and resident memory are both smaller than an equivalent Spring Boot application configured the default way. As a native executable, the same closed-world analysis is extended one step further by GraalVM/Mandrel’s own static reachability analysis, ahead-of-time compiling only the reachable graph into a self-contained binary with startup measured in single-digit milliseconds and a fraction of the JVM process’s resident memory — at the cost of a slower build and the native-image compatibility constraints documented on that page.

Spring Boot has narrowed part of this gap without abandoning its runtime model: Spring Framework’s AOT engine (triggered via spring-boot:process-aot in the Maven plugin) precomputes bean definitions ahead of time specifically to support a GraalVM native-image build of a Spring Boot application, replacing a meaningful slice of the reflection-based discovery described above for that build. Two things have not changed: first, the default way most Spring Boot applications actually run in production is still the plain JVM path with no AOT processing, where startup and memory remain dominated by runtime classpath scanning and auto-configuration evaluation; second, even Spring’s AOT-processed native path still carries Spring’s broader dependency footprint and reflection configuration surface into the native build, which is a different starting point than a framework whose extensions were designed against the closed-world assumption from the start. Neither project publishes numbers here that this page repeats as a head-to-head benchmark — both projects' own documentation describes the mechanism, not a specific millisecond or megabyte figure to cite as representative, and any such figure depends heavily on the specific set of extensions/dependencies, JVM/GraalVM version, and hardware involved.

Two startup pipelines side by side — Quarkus’s build-time augmentation followed by static init and a thin runtime init

Developer experience

Quarkus’s inner loop is dev mode (quarkus dev): live reload that recompiles and re-augments changed code on the next incoming request, the Dev UI (/q/dev) for inspecting beans, config and extension-specific consoles in the browser, Dev Services that start (and stop) a matching container automatically the moment an unconfigured extension needs a database, broker or cache, and continuous testing that reruns only the tests affected by a change in the background as you edit.

Spring Boot’s equivalents are individually mature but less integrated into one loop: Spring Boot DevTools gives automatic restart on classpath change (a JVM restart, not Quarkus’s finer-grained hot replace) plus LiveReload browser refresh; Docker Compose support (spring-boot-docker-compose) starts services declared in a compose.yaml alongside the application and wires their connection details in automatically; and Testcontainers can be wired for dev-time use via @ServiceConnection-annotated container beans in an ordinary @TestConfiguration class, launched with a test-scoped launcher (SpringApplication.from(MyApp::main).with(MyContainersConfig.class).run(args)) that the spring-boot:test-run Maven goal runs directly, giving a dev-time container story broadly comparable to Dev Services once configured. See Unit and Integration Testing for the Spring Boot Testcontainers integration in full.

Ecosystem and standards

Spring’s breadth is its own category of advantage: essentially every enterprise Java concern — batch processing, every major NoSQL and SQL data store, messaging, security, cloud provider SDK integration — has a first-party Spring Data/Spring Cloud/Spring Security module, documented and maintained as part of one coherent umbrella project, plus Spring Cloud as a single answer for distributed-systems concerns (config, discovery, circuit breaking, gateways) that many teams standardize on wholesale rather than assembling themselves.

Quarkus’s ecosystem is standards-first rather than umbrella-first: extensions implement Jakarta EE and MicroProfile specifications wherever one exists (JAX-RS/Jakarta REST behind Quarkus REST, Jakarta Persistence behind Hibernate ORM, Jakarta Transactions behind Datasources and Transactions, MicroProfile Config/Health/Metrics/REST Client/Fault Tolerance), with SmallRye as the project providing most of those MicroProfile implementations. Beyond the core platform, Quarkiverse (covered on Extensions and the Platform) is a community-maintained extension registry filling gaps the core platform doesn’t, and Camel Quarkus (camel.apache.org/camel-quarkus) brings Apache Camel’s integration-route DSL and its very large connector catalog onto Quarkus’s build-time model for teams whose integration needs go beyond what a hand-written REST/messaging layer covers.

Hiring, community, and longevity

Spring Boot’s community and installed base are, by a wide margin, the larger of the two: more engineers have used it, more Stack Overflow questions and blog posts already exist for a given problem, more third-party libraries assume it as the default web framework to integrate with, and it is more often the framework a Java developer already knows coming into a new team. That translates directly into hiring: a team standardizing on Spring Boot draws from a larger pool of engineers who need no ramp-up, and a stuck developer is more likely to find an existing answer to a specific error message.

Quarkus’s community is smaller and younger, backed primarily by Red Hat with active contribution from IBM and the wider community, and standards-first by design — which cuts both ways for longevity. On one hand, a smaller community means fewer existing answers to an obscure problem and a shallower pool of engineers who already know the framework, so onboarding and troubleshooting both lean more heavily on the official Quarkus guides than on community content. On the other hand, because Quarkus extensions sit on top of Jakarta EE/MicroProfile standards rather than a project-specific API surface wherever a standard exists, code written against those standard APIs is inherently less tied to Quarkus specifically than Spring-idiom code is to Spring.

Pros and cons

Quarkus — real downsides:

  • A smaller ecosystem and talent pool than Spring Boot’s, discussed above.

  • The build-time, closed-world model (Build-Time Augmentation and the Closed World) complicates any third-party library that resolves behavior via unconstrained runtime reflection, dynamic classloading, or dynamic proxies it cannot see through — such a library needs an extension written for it (or a RegisterForReflection workaround) before it works at all.

  • Longer build times: augmentation, and especially a native-image build’s static reachability analysis, add real wall-clock time to every build compared to a plain javac compile.

  • Using an arbitrary third-party library well generally means finding or writing a Quarkus extension for it, rather than simply adding a dependency and letting runtime reflection sort it out.

  • Native-image debugging and observability are more limited than on the JVM — symbolic debugging, profilers and some Java Flight Recorder features work differently or not at all against a native binary, covered honestly on Native Executables with GraalVM/Mandrel.

Spring Boot — real downsides:

  • Startup time and memory footprint under default (non-AOT) configuration are both larger than Quarkus’s, because classpath scanning and auto-configuration evaluation happen at every process start rather than once at build time.

  • That same runtime discovery relies on reflection and dynamic proxying throughout, which is inherent overhead the framework pays on every start, not just a one-time cost.

  • The native-image path exists (Spring AOT + GraalVM) but is a narrower, more constrained subset of what runs reliably compared to the JVM path most Spring Boot applications actually deploy, and inherits Spring’s broader dependency footprint into that native build.

  • The configuration surface — hundreds of auto-configuration classes, each with its own @Conditional rules — is powerful but genuinely opaque: understanding why a particular bean was or was not auto-configured often means reading the auto-configuration class’s source, not just its documentation.

Which should I choose?

Scenario Guidance

Serverless / scale-to-zero

Favor Quarkus, ideally as a native executable — cold-start time and memory are exactly the costs a scale-to-zero platform bills for, and Quarkus’s build-time model minimizes both directly.

High-density Kubernetes (many small services per node)

Favor Quarkus — lower per-instance memory footprint means more instances per node at the same resource limit; see Kubernetes and OpenShift.

Long-running, high-throughput monolith

Either framework works well once warmed up — steady-state throughput is dominated by application logic and I/O, not by which framework booted the process, so this scenario should usually be decided by team/ecosystem fit rather than by startup or memory numbers.

Existing Spring codebase or team

Favor Spring Boot, or a staged migration via the Spring compatibility extensions if container density or startup time become a real constraint later — see the migration guidance below before committing to a rewrite.

Standards-driven or vendor-neutral requirements

Favor Quarkus — Jakarta EE and MicroProfile compliance means the application code is portable across any compliant runtime, not tied to one project’s API surface the way Spring-idiom code is tied to Spring.

Heavy reliance on a Spring-only library

Favor Spring Boot, unless that specific library’s behavior is already known to work under the relevant compatibility extension — a library that expects a real ApplicationContext, BeanFactoryPostProcessor hooks, or Spring AOP beyond what the compatibility layer recognizes will not work on Quarkus regardless of which extensions are added.

flowchart TD A{Cold start time or\nmemory footprint a hard\nconstraint right now?} -->|yes| B[Favor Quarkus,\nnative executable if possible] A -->|no| C{Existing Spring codebase\nor team, no standards\nmandate?} C -->|yes| D[Favor Spring Boot] C -->|no| E{Standards compliance /\nvendor neutrality required?} E -->|yes| F[Favor Quarkus] E -->|no| G{Depends heavily on a\nSpring-only library not\ncovered by compatibility\nextensions?} G -->|yes| D G -->|no| H[Either framework fits --\ndecide on team/ecosystem fit]

Migrating from Spring Boot to Quarkus

Spring API Compatibility Extensions is the reference for what a migration actually costs: it scopes each compatibility extension honestly (what it supports and what it does not), maps the same Spring idioms covered in this page’s mapping table onto their Quarkus-native equivalents, and lays out which parts of a typical Spring Boot service transfer close to annotation-for-annotation versus which parts — Spring Security’s filter-chain configuration, Spring AOP pointcuts beyond a CDI interceptor binding, direct ApplicationContext/BeanFactory calls, Spring Boot Actuator, Spring Cloud beyond its config-client extension, and WebFlux’s Mono/Flux controllers — have no compatibility-extension equivalent and must be rewritten rather than ported. Read that page before committing to either a full migration or a compatibility-extension-based incremental adoption.