Lesson 12. Quarkus vs. Spring Boot

user1272047user1272047
12 min read

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

application.properties

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. 🚀

0
Subscribe to my newsletter

Read articles from user1272047 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

user1272047
user1272047