<< All versions
Skill v1.0.1
currentAutomated scan96/100affaan-m/ecc/quarkus-patterns
+4 new
──Details
PublishedJuly 6, 2026 at 05:50 AM
Content Hashsha256:feacf215030c376a...
Git SHA4130457d674d
Bump Typepatch
──Files
Files (1 file, 20.9 KB)
SKILL.md20.9 KBactive
SKILL.md · 725 lines · 20.9 KB
version: "1.0.1" name: quarkus-patterns description: Quarkus 3.x LTS architecture patterns with Camel for messaging, RESTful API design, CDI services, data access with Panache, and async processing. Use for Java Quarkus backend work with event-driven architectures. metadata: origin: ECC
Quarkus Development Patterns
Quarkus 3.x architecture and API patterns for cloud-native, event-driven services with Apache Camel.
When to Activate
- Building REST APIs with JAX-RS or RESTEasy Reactive
- Structuring resource → service → repository layers
- Implementing event-driven patterns with Apache Camel and RabbitMQ
- Configuring Hibernate Panache, caching, or reactive streams
- Adding validation, exception mapping, or pagination
- Setting up profiles for dev/staging/production environments (YAML config)
- Custom logging with LogContext and Logback/Logstash encoder
- Working with CompletableFuture for async operations
- Implementing conditional flow processing
- Working with GraalVM native compilation
Service Layer with Multiple Dependencies
java
@Slf4j@ApplicationScoped@RequiredArgsConstructorpublic class OrderProcessingService {private final OrderValidator orderValidator;private final EventService eventService;private final OrderRepository orderRepository;private final FulfillmentPublisher fulfillmentPublisher;private final AuditPublisher auditPublisher;@Transactionalpublic OrderReceipt process(CreateOrderCommand command) {ValidationResult validation = orderValidator.validate(command);if (!validation.valid()) {eventService.createErrorEvent(command, "ORDER_REJECTED", validation.message());throw new WebApplicationException(validation.message(), Response.Status.BAD_REQUEST);}Order order = Order.from(command);orderRepository.persist(order);OrderReceipt receipt = OrderReceipt.from(order);fulfillmentPublisher.publishAsync(receipt);auditPublisher.publish("ORDER_ACCEPTED", receipt);eventService.createSuccessEvent(receipt, "ORDER_ACCEPTED");log.info("Processed order {}", order.id);return receipt;}}
Key Patterns:
@RequiredArgsConstructorfor constructor injection via Lombok@Slf4jfor Logback logging@Transactionalon service methods that write through Panache or repositories- Validate input before persistence or message publication
- Event tracking for success/error scenarios
- Async Camel message publishing
Custom Logging Context Pattern (Logback)
java
@ApplicationScopedpublic class ProcessingService {public void processDocument(Document doc) {LogContext logContext = CustomLog.getCurrentContext();try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {// Add context to all log statementslogContext.put("documentId", doc.getId().toString());logContext.put("documentType", doc.getType());logContext.put("userId", SecurityContext.getUserId());log.info("Starting document processing");// All logs within this scope inherit the contextprocessInternal(doc);log.info("Document processing completed");} catch (Exception e) {log.error("Document processing failed", e);throw e;}}}
Logback Configuration (logback.xml):
xml
<configuration><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder class="net.logstash.logback.encoder.LogstashEncoder"><includeContext>true</includeContext><includeMdc>true</includeMdc></encoder></appender><logger name="com.example" level="INFO"/><root level="WARN"><appender-ref ref="CONSOLE"/></root></configuration>
Event Service Pattern
java
@Slf4j@ApplicationScoped@RequiredArgsConstructorpublic class EventService {private final EventRepository eventRepository;private final ObjectMapper objectMapper;public void createSuccessEvent(Object payload, String eventType) {Objects.requireNonNull(payload, "Payload cannot be null");Event event = new Event();event.setType(eventType);event.setStatus(EventStatus.SUCCESS);event.setPayload(serializePayload(payload));event.setTimestamp(Instant.now());eventRepository.persist(event);log.info("Success event created: {}", eventType);}public void createErrorEvent(Object payload, String eventType, String errorMessage) {Objects.requireNonNull(payload, "Payload cannot be null");if (errorMessage == null || errorMessage.isBlank()) {throw new IllegalArgumentException("Error message cannot be blank");}Event event = new Event();event.setType(eventType);event.setStatus(EventStatus.ERROR);event.setErrorMessage(errorMessage);event.setPayload(serializePayload(payload));event.setTimestamp(Instant.now());eventRepository.persist(event);log.error("Error event created: {} - {}", eventType, errorMessage);}private String serializePayload(Object payload) {try {return objectMapper.writeValueAsString(payload);} catch (JsonProcessingException e) {throw new IllegalStateException("Failed to serialize event payload", e);}}}
Camel Message Publishing (RabbitMQ)
java
@Slf4j@ApplicationScoped@RequiredArgsConstructorpublic class BusinessRulesPublisher {private final ProducerTemplate producerTemplate;public void publishSync(BusinessRulesPayload payload) {producerTemplate.sendBody("direct:business-rules-publisher",payload);}}
Camel Route Configuration:
java
@ApplicationScopedpublic class BusinessRulesRoute extends RouteBuilder {@ConfigProperty(name = "camel.rabbitmq.queue.business-rules")String businessRulesQueue;@ConfigProperty(name = "rabbitmq.host")String rabbitHost;@ConfigProperty(name = "rabbitmq.port")Integer rabbitPort;@Overridepublic void configure() {from("direct:business-rules-publisher").routeId("business-rules-publisher").log("Publishing message to RabbitMQ: ${body}").marshal().json(JsonLibrary.Jackson).toF("spring-rabbitmq:%s?hostname=%s&portNumber=%d",businessRulesQueue, rabbitHost, rabbitPort);}}
Camel Direct Routes (In-Memory)
java
@ApplicationScopedpublic class DocumentProcessingRoute extends RouteBuilder {@Overridepublic void configure() {// Error handlingonException(ValidationException.class).handled(true).to("direct:validation-error-handler").log("Validation error: ${exception.message}");// Main processing routefrom("direct:process-document").routeId("document-processing").log("Processing document: ${header.documentId}").bean(DocumentValidator.class, "validate").bean(DocumentTransformer.class, "transform").choice().when(header("documentType").isEqualTo("INVOICE")).to("direct:process-invoice").when(header("documentType").isEqualTo("CREDIT_NOTE")).to("direct:process-credit-note").otherwise().to("direct:process-generic").end();from("direct:validation-error-handler").bean(EventService.class, "createErrorEvent").log("Validation error handled");}}
Camel File Processing
java
@ApplicationScopedpublic class FileMonitoringRoute extends RouteBuilder {@ConfigProperty(name = "file.input.directory")String inputDirectory;@ConfigProperty(name = "file.processed.directory")String processedDirectory;@ConfigProperty(name = "file.error.directory")String errorDirectory;@Overridepublic void configure() {from("file:" + inputDirectory + "?move=" + processedDirectory +"&moveFailed=" + errorDirectory + "&delay=5000").routeId("file-monitor").log("Processing file: ${header.CamelFileName}").to("direct:process-file");from("direct:process-file").bean(OrderProcessingService.class, "processFile").log("File processing completed");}}
Camel Bean Invocation
java
@ApplicationScopedpublic class InvoiceRoute extends RouteBuilder {@Overridepublic void configure() {from("direct:invoice-validation").bean(InvoiceFlowValidator.class, "validateFlowWithConfig").log("Validation result: ${body}");from("direct:persist-and-publish").bean(DocumentJobService.class, "createDocumentAndJobEntities").bean(BusinessRulesPublisher.class, "publishAsync").bean(EventService.class, "createSuccessEvent(${body}, 'PUBLISHED')");}}
REST API Structure
java
@Path("/api/documents")@Produces(MediaType.APPLICATION_JSON)@Consumes(MediaType.APPLICATION_JSON)@RequiredArgsConstructorpublic class DocumentResource {private final DocumentService documentService;@GETpublic Response list(@QueryParam("page") @DefaultValue("0") int page,@QueryParam("size") @DefaultValue("20") int size) {List<Document> documents = documentService.list(page, size);return Response.ok(documents).build();}@POSTpublic Response create(@Valid CreateDocumentRequest request, @Context UriInfo uriInfo) {Document document = documentService.create(request);URI location = uriInfo.getAbsolutePathBuilder().path(String.valueOf(document.id)).build();return Response.created(location).entity(DocumentResponse.from(document)).build();}@GET@Path("/{id}")public Response getById(@PathParam("id") Long id) {return documentService.findById(id).map(DocumentResponse::from).map(Response::ok).orElse(Response.status(Response.Status.NOT_FOUND)).build();}}
Repository Pattern (Panache Repository)
java
@ApplicationScopedpublic class DocumentRepository implements PanacheRepository<Document> {public List<Document> findByStatus(DocumentStatus status, int page, int size) {return find("status = ?1 order by createdAt desc", status).page(page, size).list();}public Optional<Document> findByReferenceNumber(String referenceNumber) {return find("referenceNumber", referenceNumber).firstResultOptional();}public long countByStatusAndDate(DocumentStatus status, LocalDate date) {return count("status = ?1 and createdAt >= ?2", status, date.atStartOfDay());}}
Service Layer with Transactions
java
@ApplicationScoped@RequiredArgsConstructorpublic class DocumentService {private final DocumentRepository repo;private final EventService eventService;@Transactionalpublic Document create(CreateDocumentRequest request) {Document document = new Document();document.setReferenceNumber(request.referenceNumber());document.setDescription(request.description());document.setStatus(DocumentStatus.PENDING);document.setCreatedAt(Instant.now());repo.persist(document);eventService.createSuccessEvent(document, "DOCUMENT_CREATED");return document;}public Optional<Document> findById(Long id) {return repo.findByIdOptional(id);}public List<Document> list(int page, int size) {return repo.findAll().page(page, size).list();}}
DTOs and Validation
java
public record CreateDocumentRequest(@NotBlank @Size(max = 200) String referenceNumber,@NotBlank @Size(max = 2000) String description,@NotNull @FutureOrPresent Instant validUntil,@NotEmpty List<@NotBlank String> categories) {}public record DocumentResponse(Long id, String referenceNumber, DocumentStatus status) {public static DocumentResponse from(Document document) {return new DocumentResponse(document.getId(), document.getReferenceNumber(),document.getStatus());}}
Exception Mapping
java
@Providerpublic class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {@Overridepublic Response toResponse(ConstraintViolationException exception) {String message = exception.getConstraintViolations().stream().map(cv -> cv.getPropertyPath() + ": " + cv.getMessage()).collect(Collectors.joining(", "));return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "validation_error", "message", message)).build();}}@Provider@Slf4jpublic class GenericExceptionMapper implements ExceptionMapper<Exception> {@Overridepublic Response toResponse(Exception exception) {log.error("Unhandled exception", exception);return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", "internal_error", "message", "An unexpected error occurred")).build();}}
CompletableFuture Async Operations
java
@Slf4j@ApplicationScoped@RequiredArgsConstructorpublic class FileStorageService {private final S3Client s3Client;private final ExecutorService executorService;@ConfigProperty(name = "storage.bucket-name")String bucketName;public CompletableFuture<StoredDocumentInfo> uploadOriginalFile(InputStream inputStream,long size,LogContext logContext,InvoiceFormat format) {return CompletableFuture.supplyAsync(() -> {try (SafeAutoCloseable ignored = CustomLog.startScope(logContext)) {String path = generateStoragePath(format);PutObjectRequest request = PutObjectRequest.builder().bucket(bucketName).key(path).contentLength(size).build();s3Client.putObject(request, RequestBody.fromInputStream(inputStream, size));log.info("File uploaded to S3: {}", path);return new StoredDocumentInfo(path, size, Instant.now());} catch (Exception e) {log.error("Failed to upload file to S3", e);throw new StorageException("Upload failed", e);}}, executorService);}}
Caching
java
@ApplicationScoped@RequiredArgsConstructorpublic class DocumentCacheService {private final DocumentRepository repo;@CacheResult(cacheName = "document-cache")public Optional<Document> getById(@CacheKey Long id) {return repo.findByIdOptional(id);}@CacheInvalidate(cacheName = "document-cache")public void evict(@CacheKey Long id) {}@CacheInvalidateAll(cacheName = "document-cache")public void evictAll() {}}
Configuration as YAML
yaml
# application.yml"%dev":quarkus:datasource:jdbc:url: jdbc:postgresql://localhost:5432/dev_dbusername: dev_userpassword: ${DB_PASSWORD}hibernate-orm:database:generation: drop-and-createrabbitmq:host: localhostport: 5672username: ${RABBITMQ_USER}password: ${RABBITMQ_PASSWORD}"%test":quarkus:datasource:jdbc:url: jdbc:h2:mem:testhibernate-orm:database:generation: drop-and-create"%prod":quarkus:datasource:jdbc:url: ${DATABASE_URL}username: ${DB_USER}password: ${DB_PASSWORD}hibernate-orm:database:generation: validaterabbitmq:host: ${RABBITMQ_HOST}port: ${RABBITMQ_PORT}username: ${RABBITMQ_USER}password: ${RABBITMQ_PASSWORD}# Camel configurationcamel:rabbitmq:queue:business-rules: business-rules-queueinvoice-processing: invoice-processing-queue
Health Checks
java
@Readiness@ApplicationScoped@RequiredArgsConstructorpublic class DatabaseHealthCheck implements HealthCheck {private final AgroalDataSource dataSource;@Overridepublic HealthCheckResponse call() {try (Connection conn = dataSource.getConnection()) {boolean valid = conn.isValid(2);return HealthCheckResponse.named("Database connection").status(valid).build();} catch (SQLException e) {return HealthCheckResponse.down("Database connection");}}}@Liveness@ApplicationScopedpublic class CamelHealthCheck implements HealthCheck {@InjectCamelContext camelContext;@Overridepublic HealthCheckResponse call() {boolean isStarted = camelContext.getStatus().isStarted();return HealthCheckResponse.named("Camel Context").status(isStarted).build();}}
Dependencies (Maven)
xml
<properties><quarkus.platform.version>3.27.0</quarkus.platform.version><lombok.version>1.18.42</lombok.version><assertj-core.version>3.24.2</assertj-core.version><jacoco-maven-plugin.version>0.8.13</jacoco-maven-plugin.version><maven.compiler.release>17</maven.compiler.release></properties><dependencyManagement><dependencies><dependency><groupId>io.quarkus.platform</groupId><artifactId>quarkus-bom</artifactId><version>${quarkus.platform.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>io.quarkus.platform</groupId><artifactId>quarkus-camel-bom</artifactId><version>${quarkus.platform.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!-- Quarkus Core --><dependency><groupId>io.quarkus</groupId><artifactId>quarkus-arc</artifactId></dependency><dependency><groupId>io.quarkus</groupId><artifactId>quarkus-config-yaml</artifactId></dependency><!-- Camel Extensions --><dependency><groupId>org.apache.camel.quarkus</groupId><artifactId>camel-quarkus-spring-rabbitmq</artifactId></dependency><dependency><groupId>org.apache.camel.quarkus</groupId><artifactId>camel-quarkus-direct</artifactId></dependency><dependency><groupId>org.apache.camel.quarkus</groupId><artifactId>camel-quarkus-bean</artifactId></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><scope>provided</scope></dependency><!-- Logging --><dependency><groupId>io.quarkiverse.logging.logback</groupId><artifactId>quarkus-logging-logback</artifactId></dependency><dependency><groupId>net.logstash.logback</groupId><artifactId>logstash-logback-encoder</artifactId></dependency></dependencies>
Best Practices
Architecture
- Use
@RequiredArgsConstructorwith Lombok for constructor injection - Keep service layer thin; delegate complex logic to specialized classes
- Use Camel routes for message routing and integration patterns
- Prefer Panache Repository pattern for data access
Event-Driven
- Always track operations with EventService (success/error events)
- Use Camel
direct:endpoints for in-memory routing - Use
spring-rabbitmqcomponent for RabbitMQ integration - Implement async publishing with
ProducerTemplate.asyncSendBody()
Logging
- Use Logback with Logstash encoder for structured logging
- Propagate LogContext through service calls with
SafeAutoCloseable - Add contextual information to LogContext for request tracing
- Use
@Slf4jinstead of manual logger instantiation
Async Operations
- Use CompletableFuture for non-blocking I/O operations
- Call
.join()when you need to wait for completion - Handle exceptions from CompletableFuture properly
- Pass LogContext to async operations for tracing
Configuration
- Use YAML configuration (
quarkus-config-yaml) - Profile-aware configuration for dev/test/prod environments
- Externalize sensitive configuration to environment variables
- Use
@ConfigPropertyfor type-safe config injection
Validation
- Validate at resource layer with
@Valid - Use Bean Validation annotations on DTOs
- Map exceptions to proper HTTP responses with
@Provider
Transactions
- Use
@Transactionalon service methods that modify data - Keep transactions short and focused
- Avoid calling async operations within transactions
Testing
- Use
camel-quarkus-junit5for route testing - Use AssertJ for assertions
- Mock all external dependencies
- Test conditional flow logic thoroughly
Quarkus-Specific
- Stay on latest LTS version (3.x)
- Use Quarkus dev mode for hot reload
- Add health checks for production readiness
- Test native compilation compatibility periodically