Lesson 12. Quarkus vs. Spring Boot

Table of contents
- 1. Dependency Injection (CDI in Quarkus vs @Service in Spring Boot)
- 2. REST Endpoints (JAX-RS in Quarkus vs Spring MVC in Spring Boot)
- 3. Configuration (MicroProfile Config vs @Value)
- 4. REST API with Response Headers (Quarkus @RestResponse vs Spring Boot ResponseEntity)
- 5. File Upload (Quarkus RESTEasy Multipart vs Spring Boot MultipartFile)
- 6. Exception Handling (Quarkus JAX-RS @Provider vs Spring Boot @ControllerAdvice)
- 7. Database Access (Quarkus Panache vs Spring Data JPA)
- 8. Scheduled Tasks (Quarkus @Scheduled vs Spring Boot @Scheduled)
- 9. WebSockets (Quarkus vs Spring Boot)
- 10. Security (Quarkus Security vs Spring Security)
- Conclusion
1. Dependency Injection (CDI in Quarkus vs @Service in Spring Boot)
🔹 Quarkus Example: CDI (Jakarta Dependency Injection)
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingService {
public String greet(String name) {
return "Hello, " + name;
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses @ApplicationScoped
, while Spring Boot uses @Service
.
2️⃣ Quarkus CDI works at build time, improving startup speed.
3️⃣ Spring Boot DI uses runtime reflection, increasing memory usage.
4️⃣ Quarkus optimizes for GraalVM, making native compilation easier.
5️⃣ Spring Boot provides more DI flexibility, but with higher overhead.
📌 Code Explanation (Quarkus)
1️⃣ @ApplicationScoped
makes the bean available application-wide.
2️⃣ The method greet()
returns a simple greeting message.
3️⃣ No need for @Inject
when using the default constructor.
4️⃣ CDI manages lifecycle, ensuring singleton-like behavior.
5️⃣ Works with Quarkus' lazy class loading, improving efficiency.
🔹 Spring Boot Example: Using @Service
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public String greet(String name) {
return "Hello, " + name;
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot uses @Service
instead of @ApplicationScoped
.
2️⃣ DI in Spring Boot is runtime-based, increasing startup time.
3️⃣ Spring Boot beans are managed by the Spring container.
4️⃣ Requires @Autowired
for dependency injection in some cases.
5️⃣ Heavier but integrates well with Spring Boot's ecosystem.
📌 Code Explanation (Spring Boot)
1️⃣ @Service
marks this class as a service component.
2️⃣ The method greet()
returns a string message.
3️⃣ Spring automatically manages the lifecycle of this bean.
4️⃣ Injectable into controllers or other components.
5️⃣ Requires no additional setup, as Spring scans it automatically.
2. REST Endpoints (JAX-RS in Quarkus vs Spring MVC in Spring Boot)
🔹 Quarkus Example: JAX-RS
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class HelloResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello, Quarkus!";
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses JAX-RS, while Spring Boot uses Spring MVC.
2️⃣ JAX-RS annotations work without Spring-specific classes.
3️⃣ Quarkus REST endpoints compile at build-time, improving performance.
4️⃣ Spring Boot uses @RestController
, relying on runtime reflection.
5️⃣ Quarkus eliminates the need for a servlet container, reducing memory.
📌 Code Explanation (Quarkus)
1️⃣ @Path("/hello")
defines the endpoint URL.
2️⃣ @GET
maps HTTP GET requests to the method.
3️⃣ @Produces(MediaType.TEXT_PLAIN)
returns plain text responses.
4️⃣ Fast, lightweight, and efficient for cloud applications.
5️⃣ No extra dependencies required, as JAX-RS is built-in.
🔹 Spring Boot Example: Using @RestController
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping
public String hello() {
return "Hello, Spring Boot!";
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot uses @RestController
, which includes @ResponseBody
.
2️⃣ Uses @RequestMapping
instead of @Path
.
3️⃣ Spring Boot requires a servlet container, increasing overhead.
4️⃣ @GetMapping
is equivalent to @GET
in JAX-RS.
5️⃣ More compatible with existing Spring applications.
📌 Code Explanation (Spring Boot)
1️⃣ @RestController
defines this as a REST API controller.
2️⃣ @RequestMapping("/hello")
sets the base URL for this resource.
3️⃣ @GetMapping
maps HTTP GET requests to this method.
4️⃣ Spring Boot handles JSON responses automatically.
5️⃣ Easier for Spring developers, but has higher memory usage.
3. Configuration (MicroProfile Config vs @Value
)
🔹 Quarkus Example: MicroProfile Config
greeting.message=Hello from Quarkus!
import org.eclipse.microprofile.config.inject.ConfigProperty;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingConfig {
@ConfigProperty(name = "greeting.message")
String message;
public String getMessage() {
return message;
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses MicroProfile Config, while Spring Boot uses @Value
.
2️⃣ MicroProfile Config is more cloud-friendly, supporting ConfigMaps.
3️⃣ Quarkus loads properties at build-time, improving performance.
4️⃣ Spring Boot provides more flexible property binding.
5️⃣ Quarkus supports environment variables seamlessly.
📌 Code Explanation (Quarkus)
1️⃣ @ConfigProperty
injects configuration values from application.properties
.
2️⃣ Defines message
as a class variable.
3️⃣ Works without @Inject
due to Quarkus CDI optimizations.
4️⃣ Auto-wires configuration values at startup.
5️⃣ Reduces dependency on external configuration files.
🔹 Spring Boot Example: Using @Value
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class GreetingConfig {
@Value("${greeting.message}")
private String message;
public String getMessage() {
return message;
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot uses @Value
to inject configuration values.
2️⃣ Requires @Component
annotation to register the class as a bean.
3️⃣ Property binding happens at runtime, affecting startup time.
4️⃣ Supports .properties
and .yml
formats, making it flexible.
5️⃣ Quarkus processes values faster at build-time.
📌 Code Explanation (Spring Boot)
1️⃣ @Value("${greeting.message}")
binds properties dynamically.
2️⃣ The variable message
stores the injected value.
3️⃣ @Component
registers this class as a Spring-managed bean.
4️⃣ Values can be overridden using environment variables.
5️⃣ Works well in Spring Boot’s ecosystem but is slower than Quarkus.
4. REST API with Response Headers (Quarkus @RestResponse
vs Spring Boot ResponseEntity
)
🔹 Quarkus Example: Setting Response Headers
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
@Path("/headers")
public class HeaderResource {
@GET
public Response getHeaders() {
return Response.ok("Header Example")
.header("X-Custom-Header", "Quarkus")
.build();
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses JAX-RS Response
, while Spring Boot uses ResponseEntity<T>
.
2️⃣ Quarkus optimizes the response at build-time, reducing runtime overhead.
3️⃣ Spring Boot dynamically builds responses, making it more flexible.
4️⃣ Quarkus is lightweight, avoiding unnecessary configurations.
5️⃣ Spring Boot offers better HTTP abstraction support.
📌 Code Explanation (Quarkus)
1️⃣ @Path("/headers")
defines a REST endpoint.
2️⃣ @GET
marks the method as an HTTP GET request.
3️⃣ Response.ok("Header Example")
creates an HTTP 200 response.
4️⃣ .header("X-Custom-Header", "Quarkus")
adds a custom header.
5️⃣ .build()
finalizes the response.
🔹 Spring Boot Example: Setting Response Headers
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/headers")
public class HeaderController {
@GetMapping
public ResponseEntity<String> getHeaders() {
return ResponseEntity.ok()
.header("X-Custom-Header", "SpringBoot")
.body("Header Example");
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot uses ResponseEntity<T>
, a flexible response wrapper.
2️⃣ Spring Boot allows setting headers dynamically at runtime.
3️⃣ Quarkus compiles REST logic at build-time for performance.
4️⃣ Spring Boot requires more annotations and boilerplate.
5️⃣ Quarkus keeps responses minimal and efficient.
📌 Code Explanation (Spring Boot)
1️⃣ @RestController
marks the class as a REST API controller.
2️⃣ @RequestMapping("/headers")
maps the base path.
3️⃣ @GetMapping
defines the HTTP GET method.
4️⃣ ResponseEntity.ok()
creates a 200 response.
5️⃣ .header("X-Custom-Header", "SpringBoot")
adds a custom header.
5. File Upload (Quarkus RESTEasy Multipart vs Spring Boot MultipartFile)
🔹 Quarkus Example: RESTEasy Multipart File Upload
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
@Path("/upload")
public class FileUploadResource {
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@MultipartForm FileUploadForm form) {
return Response.ok("File received: " + form.fileName).build();
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses RESTEasy MultipartForm
, while Spring Boot uses MultipartFile
.
2️⃣ Quarkus minimizes reflection, making it faster for cloud deployments.
3️⃣ Spring Boot provides MultipartFile
, a more intuitive interface.
4️⃣ Quarkus has a more lightweight approach, reducing dependencies.
5️⃣ Spring Boot integrates better with Java Servlet API.
📌 Code Explanation (Quarkus)
1️⃣ @Path("/upload")
defines the upload endpoint.
2️⃣ @POST
marks the HTTP method as POST.
3️⃣ @Consumes(MediaType.MULTIPART_FORM_DATA)
expects multipart form data.
4️⃣ @MultipartForm FileUploadForm form
handles file uploads.
5️⃣ Response.ok("File received: " + form.fileName)
returns success.
🔹 Spring Boot Example: Using MultipartFile
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/upload")
public class FileUploadController {
@PostMapping
public String uploadFile(@RequestParam("file") MultipartFile file) {
return "File received: " + file.getOriginalFilename();
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot uses MultipartFile
, simplifying file handling.
2️⃣ Spring Boot requires spring-boot-starter-web
for file uploads.
3️⃣ Quarkus relies on RESTEasy, keeping dependencies minimal.
4️⃣ Spring Boot supports automatic file storage with minimal config.
5️⃣ Quarkus requires manual parsing of multipart data.
📌 Code Explanation (Spring Boot)
1️⃣ @RestController
marks the class as a controller.
2️⃣ @RequestMapping("/upload")
sets the base path.
3️⃣ @PostMapping
defines a POST request.
4️⃣ @RequestParam("file") MultipartFile file
handles the uploaded file.
5️⃣ file.getOriginalFilename()
retrieves the uploaded file’s name.
6. Exception Handling (Quarkus JAX-RS @Provider
vs Spring Boot @ControllerAdvice
)
🔹 Quarkus Example: JAX-RS Exception Mapper
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class CustomExceptionMapper implements ExceptionMapper<RuntimeException> {
@Override
public Response toResponse(RuntimeException exception) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Custom error: " + exception.getMessage())
.build();
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses JAX-RS @Provider
, while Spring Boot uses @ControllerAdvice
.
2️⃣ Exception handling is build-time optimized in Quarkus, improving performance.
3️⃣ Spring Boot allows handling exceptions globally for multiple controllers.
4️⃣ Quarkus focuses on REST-specific error handling, making it lightweight.
5️⃣ Spring Boot integrates deeply with MVC, but at the cost of performance.
📌 Code Explanation (Quarkus)
1️⃣ @Provider
registers the class as a global exception handler.
2️⃣ ExceptionMapper<RuntimeException>
maps RuntimeException
to a response.
3️⃣ toResponse()
formats the response as 400 BAD REQUEST
.
4️⃣ Lightweight and efficient, designed for REST endpoints.
5️⃣ Quarkus builds this at compile-time, reducing reflection overhead.
🔹 Spring Boot Example: Using @ControllerAdvice
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleRuntimeException(RuntimeException ex) {
return "Custom error: " + ex.getMessage();
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot uses @ControllerAdvice
to handle exceptions globally.
2️⃣ Exception handling happens at runtime, increasing startup time.
3️⃣ Works across multiple controllers, making it flexible.
4️⃣ Uses Spring MVC mechanisms, adding additional overhead.
5️⃣ Quarkus compiles exception handling at build-time, making it faster.
📌 Code Explanation (Spring Boot)
1️⃣ @ControllerAdvice
marks this class as a global exception handler.
2️⃣ @ExceptionHandler(RuntimeException.class)
catches runtime exceptions.
3️⃣ @ResponseStatus(HttpStatus.BAD_REQUEST)
sets HTTP status to 400.
4️⃣ The method returns a custom error message.
5️⃣ Spring Boot dynamically scans this at runtime.
7. Database Access (Quarkus Panache vs Spring Data JPA)
🔹 Quarkus Example: Panache Repository
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
@Entity
public class Person extends PanacheEntity {
public String name;
}
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses Panache, simplifying entity management.
2️⃣ No need for explicit repository methods, thanks to Panache defaults.
3️⃣ Spring Boot uses Spring Data JPA, requiring more boilerplate code.
4️⃣ Quarkus optimizes Hibernate at build-time, making it faster.
5️⃣ Spring Boot supports more database types out-of-the-box.
📌 Code Explanation (Quarkus)
1️⃣ PanacheEntity
removes the need for explicit IDs.
2️⃣ Automatically provides CRUD methods (e.g., findAll()
).
3️⃣ PersonRepository
extends PanacheRepository
, eliminating boilerplate code.
4️⃣ Less configuration needed, making it developer-friendly.
5️⃣ Supports native queries and complex ORM mappings.
🔹 Spring Boot Example: Spring Data JPA
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
import org.springframework.data.jpa.repository.JpaRepository;
public interface PersonRepository extends JpaRepository<Person, Long> {
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot uses JpaRepository
, requiring explicit repositories.
2️⃣ Requires @Id
and @GeneratedValue
, unlike Panache.
3️⃣ Panache provides default methods without defining an interface.
4️⃣ Spring Data JPA works at runtime, making it slower than Quarkus.
5️⃣ Spring Boot provides more out-of-the-box database integrations.
📌 Code Explanation (Spring Boot)
1️⃣ @Entity
marks the class as a database entity.
2️⃣ @Id
defines the primary key.
3️⃣ @GeneratedValue
automatically generates IDs.
4️⃣ JpaRepository<Person, Long>
enables CRUD operations.
5️⃣ Spring Boot dynamically scans repositories at runtime.
8. Scheduled Tasks (Quarkus @Scheduled
vs Spring Boot @Scheduled
)
🔹 Quarkus Example: @Scheduled
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class TaskScheduler {
@Scheduled(every = "10s")
void execute() {
System.out.println("Task executed every 10 seconds");
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses MicroProfile Scheduler, while Spring Boot uses @Scheduled
.
2️⃣ Quarkus schedules tasks at build-time, improving efficiency.
3️⃣ Spring Boot schedules at runtime, increasing overhead.
4️⃣ Quarkus is more lightweight, making it ideal for cloud applications.
5️⃣ Spring Boot offers more scheduling options.
📌 Code Explanation (Quarkus)
1️⃣ @ApplicationScoped
marks the class as a bean.
2️⃣ @Scheduled(every = "10s")
executes the method every 10 seconds.
3️⃣ Works seamlessly with Quarkus runtime.
4️⃣ No extra configuration needed.
5️⃣ Optimized for performance and minimal memory usage.
🔹 Spring Boot Example: @Scheduled
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class TaskScheduler {
@Scheduled(fixedRate = 10000)
public void execute() {
System.out.println("Task executed every 10 seconds");
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot uses @Component
, while Quarkus uses @ApplicationScoped
.
2️⃣ fixedRate
in Spring Boot is equivalent to every
in Quarkus.
3️⃣ Spring Boot schedules tasks at runtime, increasing resource usage.
4️⃣ Quarkus compiles scheduling logic at build-time.
5️⃣ Spring Boot supports cron expressions better.
📌 Code Explanation (Spring Boot)
1️⃣ @Component
registers the class as a Spring bean.
2️⃣ @Scheduled(fixedRate = 10000)
runs the task every 10 seconds.
3️⃣ Requires enabling scheduling in @SpringBootApplication
.
4️⃣ Executes using Spring Boot’s built-in scheduler.
5️⃣ Works well for traditional server applications.
9. WebSockets (Quarkus vs Spring Boot)
🔹 Quarkus Example: WebSocket Server
import jakarta.websocket.OnMessage;
import jakarta.websocket.server.ServerEndpoint;
@ServerEndpoint("/ws")
public class WebSocketEndpoint {
@OnMessage
public String onMessage(String message) {
return "Received: " + message;
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses JSR 356, while Spring Boot uses Spring WebSocket.
2️⃣ Quarkus WebSockets are lightweight, running without additional dependencies.
3️⃣ Spring Boot requires STOMP or SockJS for advanced features.
4️⃣ Quarkus WebSockets integrate well with native images.
5️⃣ Spring Boot provides more WebSocket extensions.
📌 Code Explanation (Quarkus)
1️⃣ @ServerEndpoint("/ws")
defines the WebSocket endpoint.
2️⃣ @OnMessage
handles incoming messages.
3️⃣ Lightweight and high-performance.
4️⃣ No external dependencies required.
5️⃣ Works seamlessly with cloud-native apps.
10. Security (Quarkus Security vs Spring Security)
🔹 Quarkus Example: Using JWT Authentication
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.Context;
@Path("/secure")
public class SecureResource {
@GET
@RolesAllowed("user")
public String getSecureMessage(@Context SecurityContext ctx) {
return "Hello, " + ctx.getUserPrincipal().getName();
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Quarkus uses MicroProfile JWT, while Spring Boot uses Spring Security.
2️⃣ Quarkus compiles security logic at build-time.
3️⃣ Spring Boot dynamically loads security configurations.
4️⃣ Quarkus is optimized for microservices, while Spring Boot is better for monoliths.
5️⃣ Spring Boot supports OAuth, JWT, and other mechanisms out-of-the-box.
📌 Code Explanation (Quarkus)
1️⃣ @Path("/secure")
defines the secure endpoint.
2️⃣ @GET
marks this as an HTTP GET method.
3️⃣ @RolesAllowed("user")
restricts access to authenticated users.
4️⃣ @Context SecurityContext
injects security context.
5️⃣ ctx.getUserPrincipal().getName()
retrieves the logged-in user’s name.
🔹 Spring Boot Example: Using Spring Security
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@RestController
@RequestMapping("/secure")
public class SecureController {
@GetMapping
public String getSecureMessage() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return "Hello, " + auth.getName();
}
}
📌 Difference (Quarkus vs Spring Boot)
1️⃣ Spring Boot relies on Spring Security framework.
2️⃣ Spring Boot supports multiple authentication providers.
3️⃣ Quarkus uses MicroProfile JWT for lightweight authentication.
4️⃣ Spring Boot integrates well with OAuth2 and SAML.
5️⃣ Quarkus compiles security rules at build-time.
📌 Code Explanation (Spring Boot)
1️⃣ @RestController
marks this as a REST controller.
2️⃣ @RequestMapping("/secure")
defines the endpoint path.
3️⃣ @GetMapping
maps to HTTP GET requests.
4️⃣ SecurityContextHolder.getContext().getAuthentication()
retrieves the current user.
5️⃣ auth.getName()
returns the logged-in user's name.
Conclusion
✅ Quarkus is optimized for cloud, serverless, and native compilation with GraalVM.
✅ Spring Boot is more flexible but has higher startup and memory overhead.
✅ Quarkus is build-time optimized, while Spring Boot relies more on runtime reflection.
✅ Quarkus works better for microservices, while Spring Boot integrates well with monolithic applications.
✅ Choose Quarkus for speed and efficiency, and Spring Boot for ecosystem maturity. 🚀
Subscribe to my newsletter
Read articles from user1272047 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
