Introduction to CDI: Modern Dependency Injection for WildFly and Quarkus
jakarta.inject.* and jakarta.enterprise.context.* namespaces. While WildFly utilizes Weld to evaluate and bind beans dynamically at application boot time, Quarkus implements ArC, performing build-time dependency analysis to eliminate reflection and deliver sub-second startup times.
In enterprise Java architecture, Contexts and Dependency Injection (CDI) serves as the foundational glue binding business services, security contexts, persistence adapters, and REST endpoints together. Initially introduced with Java EE 6 (JSR-299) and matured through Jakarta EE 10 and 11 (CDI 4.0 / 4.1), CDI offers a type-safe, extensible programming model.
Whether you deploy traditional application archives to a robust application server like WildFly or assemble lightning-fast container images using Quarkus, understanding CDI fundamentals is essential for any modern enterprise Java engineer.
1. Core Concepts & Bean Scopes
At its heart, any regular Java class can become a CDI-managed bean simply by having a scope annotation attached to it. The container manages bean lifecycles and instantiates dependencies as required.
The standard scopes residing in jakarta.enterprise.context.* include:
@ApplicationScoped: A single shared instance lives across the entire application runtime.@RequestScoped: Bound to the lifecycle of an individual HTTP or messaging invocation.@SessionScoped: Survives across multiple HTTP interactions for a specific user session.@Dependent: The default scope when no annotation is specified. The bean adopts the lifecycle of the parent bean into which it is injected.
package com.mastertheboss.service;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class OrderProcessingService {
public String processOrder(String orderId) {
return "Order #" + orderId + " processed successfully.";
}
}
2. Dependency Injection: Constructor vs. Field Injection
Injecting services into other managed beans is accomplished via the @Inject annotation from the jakarta.inject package. While direct field injection is common, constructor injection is the recommended best practice because it makes testing simpler and guarantees immutability.
package com.mastertheboss.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class CheckoutService {
private final OrderProcessingService orderProcessingService;
// Recommended: Constructor injection
@Inject
public CheckoutService(OrderProcessingService orderProcessingService) {
this.orderProcessingService = orderProcessingService;
}
public void checkout(String cartId) {
String result = orderProcessingService.processOrder(cartId);
System.out.println(result);
}
}
3. Disambiguating Beans with Qualifiers
When multiple implementations of the same interface exist, CDI prevents accidental ambiguity at boot time by throwing an AmbiguousResolutionException. You can resolve this cleanly by defining custom Qualifiers with @Qualifier:
package com.mastertheboss.qualifiers;
import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface MockPayment {}
Apply the qualifier to the concrete implementation and target injection point:
@ApplicationScoped
@MockPayment
public class MockPaymentGateway implements PaymentGateway {
@Override
public boolean charge(double amount) {
return true; // Test simulation
}
}
// Consuming the qualified bean:
@ApplicationScoped
public class BillingService {
@Inject
@MockPayment
PaymentGateway paymentGateway;
}
4. Producers and Event Observers
Producer Methods (@Produces)
If you need to inject third-party classes, complex objects, or dynamically configured beans that cannot be annotated directly, use a producer method:
package com.mastertheboss.config;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import java.time.format.DateTimeFormatter;
@ApplicationScoped
public class AppConfigProducer {
@Produces
@ApplicationScoped
public DateTimeFormatter standardDateFormatter() {
return DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
}
}
Decoupled Events and Observers
CDI provides a built-in event notification system allowing beans to communicate asynchronously or synchronously without compile-time coupling:
// 1. Emitting an event
@ApplicationScoped
public class UserRegistrationService {
@Inject
jakarta.enterprise.event.Event<String> userRegisteredEvent;
public void register(String username) {
// Business logic here...
userRegisteredEvent.fire(username);
}
}
// 2. Observing the event
@ApplicationScoped
public class AuditLogService {
public void onUserRegistered(@jakarta.enterprise.event.Observes String username) {
System.out.println("Audit event recorded for: " + username);
}
}
5. CDI on WildFly: Runtime Injection with Weld
WildFly implements full Jakarta EE specifications using JBoss Weld as its CDI reference engine. In WildFly, bean scanning occurs during archive deployment.
To enable CDI in your WAR deployment, place a beans.xml descriptor inside your archive's src/main/webapp/WEB-INF/ directory. For modern CDI (Jakarta EE 10+), configure bean-discovery-mode="annotated":
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0"
bean-discovery-mode="annotated">
</beans>
With bean-discovery-mode="annotated", Weld only registers classes with explicit bean-defining annotations (such as @ApplicationScoped or @RequestScoped), reducing deployment footprint and startup memory overhead.
6. CDI on Quarkus: Build-Time Injection with ArC
Unlike traditional runtime containers, Quarkus re-engineers CDI specifically for cloud-native architectures using ArC (a build-time CDI implementation based on CDI Lite):
- Zero Reflection: Bean metadata, invocation proxies, and injection points are generated as pure Java bytecode at build time.
- No beans.xml needed: Quarkus scans application dependencies automatically without requiring deployment descriptors.
- Early Failure Detection: Unresolved injection points or cyclical dependencies fail your build immediately rather than failing at startup in production.
package com.mastertheboss.rest;
import com.mastertheboss.service.CheckoutService;
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Response;
@Path("/orders")
public class OrderResource {
@Inject
CheckoutService checkoutService;
@POST
@Path("/{id}/checkout")
public Response checkoutOrder(@PathParam("id") String orderId) {
checkoutService.checkout(orderId);
return Response.accepted().build();
}
}
WildFly vs. Quarkus CDI Comparison
| Feature | WildFly (JBoss Weld) | Quarkus (ArC) |
|---|---|---|
| CDI Specification | Full CDI (Jakarta EE 10 / 11) | CDI Lite (Optimized Build-Time Profile) |
| Dependency Resolution | Runtime scanning on deployment | Compile/Build-Time pre-calculated |
Descriptor (beans.xml) |
Recommended in WEB-INF/beans.xml |
Optional (Not required) |
| Native Compilation (GraalVM) | N/A (Standard JVM execution) | First-class support out-of-the-box |
Frequently Asked Questions (FAQs)
1. Why should I migrate from javax.inject.* to jakarta.inject.*?
The javax.* namespace belongs to the legacy Java EE platform. Starting with Jakarta EE 9 and continuing through Jakarta EE 10 and 11, all standard APIs were migrated to the jakarta.* namespace. Modern versions of WildFly and Quarkus will fail to recognize or bind beans annotated with legacy javax.inject.* or javax.enterprise.* imports.
2. What happens if I forget to annotate a class with a CDI Scope?
In standard CDI, an unannotated class defaults to the @Dependent pseudo-scope. This means whenever it is injected into another bean, a new instance is created and lives only as long as the parent instance. If bean-discovery-mode="annotated" is configured (common in WildFly and Quarkus), unannotated classes will not be discovered as beans at all, resulting in deployment errors when referenced.
3. Can I use interceptors and decorators with CDI in both containers?
Yes. Both WildFly (via Weld) and Quarkus (via ArC) fully support @Interceptor and @AroundInvoke bindings to handle cross-cutting concerns like transaction handling (@Transactional), logging, and metric recording without coupling your core business logic.
Recommended Articles
Managing Quarkus Application Lifecycle with CDI Events
Learn how to leverage CDI events in Quarkus applications to manage the lifecycle of your application, from startup to shutdown.
Architecting MapStruct with CDI and Quarkus: Getting componentModel Right for Jakarta EE
Master MapStruct with CDI and Quarkus. Configure the right componentModel for Jakarta EE to build reflection-free, lightning-fast GraalVM native binaries.
A Comprehensive Comparison of WildFly Application Server and Quarkus Framework in Enterprise Java
Explore the features and use cases of WildFly and Quarkus for robust Java applications. #WildFly #Quarkus #EnterpriseJava
Exploring Quarkus vs Micronaut: Key Differences and Features for Modern Java Microservices
Discover the key differences between Quarkus and Micronaut in modern Java microservices. Learn about their core philosophies, frameworks compatibility, and more.