Quarkus Topic
Getting Started with Quarkus
What is Quarkus?
Quarkus is a Kubernetes-native Java framework tailored for GraalVM and HotSpot, designed to create supersonic, subatomic Java applications. It's built for cloud-native, containerized environments with extremely fast startup times and low memory footprint.
Key Features:
-
Fast Startup Time: 10-100x faster than traditional Java frameworks
-
Low Memory Usage: Optimized for container environments
-
Developer Joy: Live coding, unified configuration, and great tooling
-
Kubernetes Native: Built with cloud-native principles in mind
-
Standards-Based: Uses JPA, JAX-RS, CDI, Eclipse MicroProfile
1. Setting Up Quarkus
Prerequisites
-
Java 11 or 17
-
Maven 3.8+ or Gradle 7+
-
IDE (VS Code, IntelliJ, or Eclipse)
Installation Methods
Method 1: Using Quarkus CLI
bash
# Install Quarkus CLI
curl -Ls https://sh.jbang.dev | bash -s - app install --fresh --force quarkus@quarkusio
# Create a new project
quarkus create app com.example:quarkus-demo:1.0.0
cd quarkus-demo
Method 2: Using Maven
bash
mvn io.quarkus.platform:quarkus-maven-plugin:3.2.0.Final:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=quarkus-demo \
-DclassName="com.example.GreetingResource" \
-Dpath="/hello"
cd quarkus-demo
Method 3: Using start.quarkus.io
Visit start.quarkus.io and select your extensions, then download the project.
2. Project Structure
text
quarkus-demo/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/ │ │ │ ├── GreetingResource.java │ │ │ ├── entities/ │ │ │ ├── services/ │ │ │ └── repositories/ │ │ └── resources/ │ │ ├── META-INF/resources/ │ │ ├── application.properties │ │ └── import.sql │ └── test/ │ └── java/ ├── .dockerignore ├── .gitignore ├── README.md └── pom.xml
3. Basic REST API with Quarkus
Simple REST Endpoint
java
package com.example;
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 GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Quarkus REST!";
}
@GET
@Path("/{name}")
@Produces(MediaType.TEXT_PLAIN)
public String helloName(String name) {
return "Hello " + name + "!";
}
@GET
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
public Greeting jsonHello() {
return new Greeting("Hello JSON!", "Quarkus");
}
public static class Greeting {
public String message;
public String from;
public Greeting(String message, String from) {
this.message = message;
this.from = from;
}
// Getters and setters
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getFrom() { return from; }
public void setFrom(String from) { this.from = from; }
}
}
Running the Application
bash
# Development mode (with live reload)
./mvnw quarkus:dev
# The application will be available at:
# http://localhost:8080/hello
4. Configuration Management
application.properties
properties
# Server configuration
quarkus.http.port=8080
quarkus.http.host=0.0.0.0
# Logging
quarkus.log.level=INFO
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n
# Database configuration (if using H2 for development)
quarkus.datasource.db-kind=h2
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.jdbc.url=jdbc:h2:mem:quarkusdb
# Hibernate ORM
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
# Custom application properties
app.name=Quarkus Demo Application
app.version=1.0.0
app.feature.enabled=true
app.max.connections=10
# External API configuration
external.api.url=https://api.example.com
external.api.timeout=5000
external.api.retry.attempts=3
Configuration with @ConfigMapping (Type-safe)
java
package com.example.config;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithName;
import java.util.Optional;
@ConfigMapping(prefix = "app")
public interface AppConfig {
String name();
String version();
@WithName("feature.enabled")
boolean featureEnabled();
@WithName("max.connections")
int maxConnections();
ExternalApiConfig external();
interface ExternalApiConfig {
String url();
int timeout();
@WithName("retry.attempts")
int retryAttempts();
}
}
Using Configuration in Services
java
package com.example.services;
import com.example.config.AppConfig;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
public class ConfigurationService {
// Method 1: Direct injection
@ConfigProperty(name = "app.name")
String appName;
@ConfigProperty(name = "app.version", defaultValue = "1.0.0")
String appVersion;
@ConfigProperty(name = "external.api.url")
String externalApiUrl;
// Method 2: Type-safe configuration
@Inject
AppConfig appConfig;
public void printConfig() {
System.out.println("App Name: " + appName);
System.out.println("App Version: " + appVersion);
System.out.println("External API: " + externalApiUrl);
// Using type-safe config
System.out.println("Feature Enabled: " + appConfig.featureEnabled());
System.out.println("Max Connections: " + appConfig.maxConnections());
System.out.println("External API URL: " + appConfig.external().url());
}
}
5. Dependency Injection with CDI
Service Classes
java
package com.example.services;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicLong;
// @ApplicationScoped - single instance for the application
@ApplicationScoped
public class GreetingService {
private final AtomicLong counter = new AtomicLong();
public String generateGreeting(String name) {
long count = counter.incrementAndGet();
return String.format("Hello %s! (Request #%d at %s)",
name, count, LocalDateTime.now());
}
public String formalGreeting(String title, String name) {
return String.format("Good day, %s %s!", title, name);
}
}
// @RequestScoped - new instance for each request
package com.example.services;
import jakarta.enterprise.context.RequestScoped;
@RequestScoped
public class RequestScopedService {
private final String requestId = java.util.UUID.randomUUID().toString();
public String getRequestInfo() {
return "Request ID: " + requestId;
}
}
Using CDI in REST Resources
java
package com.example;
import com.example.services.GreetingService;
import com.example.services.RequestScopedService;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/greeting")
public class GreetingResource {
@Inject
GreetingService greetingService;
@Inject
RequestScopedService requestScopedService;
@GET
@Path("/{name}")
@Produces(MediaType.TEXT_PLAIN)
public String greeting(@PathParam("name") String name) {
String requestInfo = requestScopedService.getRequestInfo();
String greeting = greetingService.generateGreeting(name);
return greeting + " - " + requestInfo;
}
@GET
@Path("/formal/{title}/{name}")
@Produces(MediaType.TEXT_PLAIN)
public String formalGreeting(@PathParam("title") String title,
@PathParam("name") String name) {
return greetingService.formalGreeting(title, name);
}
}
Event-Driven with CDI
java
package com.example.events;
// Event classes
public class UserRegisteredEvent {
private final String username;
private final String email;
public UserRegisteredEvent(String username, String email) {
this.username = username;
this.email = email;
}
// getters
public String getUsername() { return username; }
public String getEmail() { return email; }
}
public class EmailSentEvent {
private final String recipient;
private final String subject;
public EmailSentEvent(String recipient, String subject) {
this.recipient = recipient;
this.subject = subject;
}
// getters
public String getRecipient() { return recipient; }
public String getSubject() { return subject; }
}
// Event producers
package com.example.services;
import com.example.events.UserRegisteredEvent;
import com.example.events.EmailSentEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;
@ApplicationScoped
public class UserRegistrationService {
@Inject
Event<UserRegisteredEvent> userRegisteredEvent;
@Inject
Event<EmailSentEvent> emailSentEvent;
public void registerUser(String username, String email) {
// Save user to database
System.out.println("Registering user: " + username);
// Fire event
userRegisteredEvent.fire(new UserRegisteredEvent(username, email));
// Fire async event
userRegisteredEvent.fireAsync(new UserRegisteredEvent(username, email));
}
}
// Event observers
package com.example.listeners;
import com.example.events.UserRegisteredEvent;
import com.example.events.EmailSentEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.event.ObservesAsync;
@ApplicationScoped
public class EventListeners {
// Synchronous observer
public void onUserRegistered(@Observes UserRegisteredEvent event) {
System.out.println("Sending welcome email to: " + event.getEmail());
// Send welcome email
}
// Asynchronous observer
public void onUserRegisteredAsync(@ObservesAsync UserRegisteredEvent event) {
System.out.println("Async: Creating user profile for: " + event.getUsername());
// Time-consuming operations
}
// Conditional observer
public void onGmailUserRegistered(@Observes @Gmail UserRegisteredEvent event) {
System.out.println("Special handling for Gmail user: " + event.getEmail());
}
}
// Qualifier for conditional events
package com.example.listeners;
import jakarta.inject.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface Gmail {
}
6. Database Integration with Panache
Entity Classes
java
package com.example.entities;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class User extends PanacheEntity {
@NotBlank
@Size(min = 3, max = 50)
public String username;
@NotBlank
@Email
public String email;
public String firstName;
public String lastName;
public LocalDateTime createdAt;
public LocalDateTime updatedAt;
// Lifecycle callbacks
public void prePersist() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
public void preUpdate() {
updatedAt = LocalDateTime.now();
}
// Static query methods
public static User findByEmail(String email) {
return find("email", email).firstResult();
}
public static long countByUsername(String username) {
return count("username", username);
}
}
// Another entity
package com.example.entities;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
public class Product extends PanacheEntity {
public String name;
public String description;
public BigDecimal price;
public Integer stockQuantity;
public LocalDateTime createdAt;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
// ... other getters/setters
}
Repository Pattern with Panache
java
package com.example.repositories;
import com.example.entities.User;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
public Optional<User> findByEmailOptional(String email) {
return find("email", email).firstResultOptional();
}
public List<User> findActiveUsers() {
return list("createdAt is not null order by createdAt desc");
}
public long updateUserEmail(Long userId, String newEmail) {
return update("email = ?1 where id = ?2", newEmail, userId);
}
public List<User> findUsersWithNameLike(String namePattern) {
return find("username like ?1", "%" + namePattern + "%").list();
}
}
package com.example.repositories;
import com.example.entities.Product;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.math.BigDecimal;
import java.util.List;
@ApplicationScoped
public class ProductRepository implements PanacheRepository<Product> {
public List<Product> findExpensiveProducts(BigDecimal minPrice) {
return find("price > ?1 order by price desc", minPrice).list();
}
public List<Product> findProductsInStock() {
return find("stockQuantity > 0").list();
}
public long updateProductPrice(Long productId, BigDecimal newPrice) {
return update("price = ?1 where id = ?2", newPrice, productId);
}
}
Service Layer
java
package com.example.services;
import com.example.entities.User;
import com.example.entities.Product;
import com.example.repositories.UserRepository;
import com.example.repositories.ProductRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class UserService {
@Inject
UserRepository userRepository;
@Inject
ProductRepository productRepository;
@Transactional
public User createUser(String username, String email, String firstName, String lastName) {
User user = new User();
user.username = username;
user.email = email;
user.firstName = firstName;
user.lastName = lastName;
userRepository.persist(user);
return user;
}
public Optional<User> getUserById(Long id) {
return userRepository.findByIdOptional(id);
}
public Optional<User> getUserByEmail(String email) {
return userRepository.findByEmailOptional(email);
}
public List<User> getAllUsers() {
return userRepository.listAll();
}
@Transactional
public boolean deleteUser(Long id) {
return userRepository.deleteById(id);
}
@Transactional
public User updateUserEmail(Long userId, String newEmail) {
Optional<User> userOpt = userRepository.findByIdOptional(userId);
if (userOpt.isPresent()) {
User user = userOpt.get();
user.email = newEmail;
return user;
}
return null;
}
}
// Product Service
package com.example.services;
import com.example.entities.Product;
import com.example.repositories.ProductRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.util.List;
@ApplicationScoped
public class ProductService {
@Inject
ProductRepository productRepository;
@Transactional
public Product createProduct(String name, String description, BigDecimal price, Integer stock) {
Product product = new Product();
product.name = name;
product.description = description;
product.price = price;
product.stockQuantity = stock;
product.createdAt = java.time.LocalDateTime.now();
productRepository.persist(product);
return product;
}
public List<Product> getAllProducts() {
return productRepository.listAll();
}
public List<Product> getProductsInStock() {
return productRepository.findProductsInStock();
}
public List<Product> getExpensiveProducts(BigDecimal minPrice) {
return productRepository.findExpensiveProducts(minPrice);
}
@Transactional
public boolean updateProductStock(Long productId, Integer newStock) {
Optional<Product> productOpt = productRepository.findByIdOptional(productId);
if (productOpt.isPresent()) {
Product product = productOpt.get();
product.stockQuantity = newStock;
return true;
}
return false;
}
}
REST Controllers for CRUD Operations
java
package com.example.resources;
import com.example.entities.User;
import com.example.services.UserService;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.net.URI;
import java.util.List;
import java.util.Optional;
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
@Inject
UserService userService;
@GET
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@GET
@Path("/{id}")
public Response getUserById(@PathParam("id") Long id) {
Optional<User> user = userService.getUserById(id);
if (user.isPresent()) {
return Response.ok(user.get()).build();
} else {
return Response.status(Response.Status.NOT_FOUND)
.entity("User not found with id: " + id)
.build();
}
}
@POST
public Response createUser(User user) {
try {
User createdUser = userService.createUser(
user.username,
user.email,
user.firstName,
user.lastName
);
return Response.created(URI.create("/api/users/" + createdUser.id))
.entity(createdUser)
.build();
} catch (Exception e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Error creating user: " + e.getMessage())
.build();
}
}
@PUT
@Path("/{id}")
public Response updateUser(@PathParam("id") Long id, User user) {
User updatedUser = userService.updateUserEmail(id, user.email);
if (updatedUser != null) {
return Response.ok(updatedUser).build();
} else {
return Response.status(Response.Status.NOT_FOUND)
.entity("User not found with id: " + id)
.build();
}
}
@DELETE
@Path("/{id}")
public Response deleteUser(@PathParam("id") Long id) {
boolean deleted = userService.deleteUser(id);
if (deleted) {
return Response.noContent().build();
} else {
return Response.status(Response.Status.NOT_FOUND)
.entity("User not found with id: " + id)
.build();
}
}
@GET
@Path("/email/{email}")
public Response getUserByEmail(@PathParam("email") String email) {
Optional<User> user = userService.getUserByEmail(email);
if (user.isPresent()) {
return Response.ok(user.get()).build();
} else {
return Response.status(Response.Status.NOT_FOUND)
.entity("User not found with email: " + email)
.build();
}
}
}
7. Testing in Quarkus
Unit Tests
java
package com.example.services;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@QuarkusTest
class GreetingServiceTest {
@Inject
GreetingService greetingService;
@Test
void testGenerateGreeting() {
String result = greetingService.generateGreeting("John");
assertTrue(result.contains("Hello John!"));
assertTrue(result.contains("Request #1"));
}
@Test
void testFormalGreeting() {
String result = greetingService.formalGreeting("Mr", "Smith");
assertEquals("Good day, Mr Smith!", result);
}
}
Integration Tests
java
package com.example.resources;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.containsString;
@QuarkusTest
class GreetingResourceTest {
@Test
void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("Hello from Quarkus REST!"));
}
@Test
void testHelloNameEndpoint() {
given()
.when().get("/hello/Quarkus")
.then()
.statusCode(200)
.body(containsString("Hello Quarkus!"));
}
@Test
void testJsonEndpoint() {
given()
.when().get("/hello/json")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("message", is("Hello JSON!"))
.body("from", is("Quarkus"));
}
}
Database Tests
java
package com.example.repositories;
import com.example.entities.User;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@QuarkusTest
@TestTransaction
class UserRepositoryTest {
@Inject
UserRepository userRepository;
@Test
void testCreateAndFindUser() {
// Create user
User user = new User();
user.username = "testuser";
user.email = "[email protected]";
user.firstName = "Test";
user.lastName = "User";
userRepository.persist(user);
assertNotNull(user.id);
// Find user
Optional<User> found = userRepository.findByEmailOptional("[email protected]");
assertTrue(found.isPresent());
assertEquals("testuser", found.get().username);
}
@Test
void testUpdateUser() {
User user = new User();
user.username = "updateuser";
user.email = "[email protected]";
userRepository.persist(user);
long updated = userRepository.updateUserEmail(user.id, "[email protected]");
assertEquals(1, updated);
Optional<User> found = userRepository.findByEmailOptional("[email protected]");
assertTrue(found.isPresent());
}
}
8. Building and Deployment
Building for Production
bash
# Build native executable (requires GraalVM)
./mvnw package -Pnative
# Build JVM mode
./mvnw package
# Build container image
./mvnw package -Dquarkus.container-image.build=true
Docker Configuration
dockerfile
# Dockerfile.jvm
FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.14
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
COPY target/quarkus-app/lib/ /deployments/lib/
COPY target/quarkus-app/*.jar /deployments/
COPY target/quarkus-app/app/ /deployments/app/
COPY target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENTRYPOINT [ "java", "-jar", "/deployments/quarkus-run.jar" ]
Kubernetes Deployment
yaml
# src/main/kubernetes.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: quarkus-demo
spec:
replicas: 3
selector:
matchLabels:
app: quarkus-demo
template:
metadata:
labels:
app: quarkus-demo
spec:
containers:
- name: quarkus-demo
image: quarkus-demo:1.0.0
ports:
- containerPort: 8080
env:
- name: QUARKUS_HTTP_PORT
value: "8080"
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: quarkus-demo
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: quarkus-demo
9. Key Quarkus Extensions
Popular Extensions to Add
bash
# RESTEasy JAX-RS
./mvnw quarkus:add-extension -Dextensions="resteasy,resteasy-jsonb"
# Hibernate ORM with Panache
./mvnw quarkus:add-extension -Dextensions="hibernate-orm-panache,jdbc-h2"
# Security
./mvnw quarkus:add-extension -Dextensions="oidc,smallrye-jwt"
# Messaging
./mvnw quarkus:add-extension -Dextensions="smallrye-reactive-messaging-kafka"
# Monitoring
./mvnw quarkus:add-extension -Dextensions="smallrye-health,smallrye-metrics"
# Database
./mvnw quarkus:add-extension -Dextensions="jdbc-postgresql"
# Testing
./mvnw quarkus:add-extension -Dextensions="rest-assured"
pom.xml Extensions Section
xml
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
</dependencies>
10. Development Workflow
Development Mode
bash
# Start in dev mode with live reload
./mvnw quarkus:dev
# Access dev UI
# http://localhost:8080/q/dev/
# Continuous testing mode
./mvnw quarkus:dev -Dquarkus.test.continuous-testing=enabled
Useful Development Endpoints
-
Dev UI:
http://localhost:8080/q/dev/ -
Health Checks:
http://localhost:8080/q/health/ -
Metrics:
http://localhost:8080/q/metrics/ -
OpenAPI:
http://localhost:8080/q/openapi/
Performance Comparison
java
// Traditional Spring Boot vs Quarkus
/*
Startup Time:
- Spring Boot: 2-5 seconds
- Quarkus JVM: 0.5-1 second
- Quarkus Native: 0.01-0.05 seconds
Memory Usage:
- Spring Boot: 100-200MB
- Quarkus JVM: 30-50MB
- Quarkus Native: 10-20MB
*/
This extensive Quarkus guide covers everything from basic setup to advanced features like CDI events, Panache ORM, testing, and deployment. Quarkus is ideal for cloud-native applications where fast startup times and low memory footprint are critical.
Comments (0)
No comments yet. Be the first to comment!