Injecting Implementations With Jakarta CDI Using Polymorphism
Use Jakarta CDI to inject implementations at runtime with Instance and qualifiers for clean, extensible, polymorphic designs without violating the Open/Closed Principle.
Join the DZone community and get the full member experience.
Join For FreeWhen building extensible and maintainable Java applications, a key challenge is choosing the right implementation of an interface without violating the Open/Closed Principle — that is, without modifying existing code whenever a new behavior is added.
In this tutorial, you’ll learn how to inject implementations using Jakarta CDI dynamically. We’ll use a simple and relatable sample (inspired by musical instruments) to illustrate polymorphism, custom qualifiers, and dynamic resolution with Instance<T>
, enabling your code to be both flexible and robust.
Most developers familiar with CDI use it for static field injection. Still, few explore its ability to resolve dependencies dynamically at runtime — a powerful feature that opens the door to more modular and adaptive designs.
Here is the code reference.
Why Static Injection Isn’t Always Enough
In typical CDI usage, developers inject dependencies statically:
@Inject
@MusicInstrument(InstrumentType.KEYBOARD)
private Instrument keyboard;
This works well when the required implementation is known at compile time. But what happens when the selection must be made based on runtime data? Many fall back to imperative logic:
@Inject
@MusicInstrument(InstrumentType.KEYBOARD)
private Instrument keyboard;
@Inject
@MusicInstrument(InstrumentType.STRING)
private Instrument string;
if (type == InstrumentType.KEYBOARD) {
return keyboard;
}
if (type == InstrumentType.STRING) {
return string;
}
This logic breaks the Open/Closed Principle, forcing you to modify your code whenever a new implementation is introduced.
The CDI Way: Flexible and Extensible
Jakarta CDI provides a better solution:
- Register multiple implementations
- Tag them with a custom qualifier
- Dynamically resolve the one you need — at runtime
Let’s see how to apply this pattern step by step.
Step-by-Step: CDI + Polymorphism + Dynamic Resolution
The first step is creating the interface:
public interface Instrument {
String play();
}
This represents a behavior that can have multiple implementations. In real systems, it could be something like PaymentProcessor
, FileExporter
, or NotificationSender
.
The second step is to define one way to differentiate those implementations. In our case, we will use an enum with the instruments type I wish to use in my band or orchestra.
public enum InstrumentType {
STRING, PERCURSSION, KEYBOARD;
}
With the enum, we will create the annotation with the enum as an attribute, where we will set the implementations.
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
public @interface MusicInstrument {
InstrumentType value();
}
Based on this annotation, we will qualify the instrument based on the type; for example, we will use the keyboard type for Piano.
@MusicInstrument(InstrumentType.KEYBOARD)
public class Piano implements Instrument {
public String play() {
return "Piano Sound";
}
}
@MusicInstrument(InstrumentType.STRING)
public class Violin implements Instrument {
public String play() {
return "Violin Sound";
}
}
@MusicInstrument(InstrumentType.PERCURSSION)
public class Xylophone implements Instrument {
public String play() {
return "Xylophone Sound";
}
}
Once we have the annotation, the types, and the implementations with the proper annotation, the next step to work is in the filter to allow dynamically to filter the class; we can do it using the AnnotationLiteral
class from CDI.
public class MusicInstrumentLiteral extends AnnotationLiteral<MusicInstrument> implements MusicInstrument {
private final InstrumentType type;
private MusicInstrumentLiteral(InstrumentType type) {
this.type = type;
}
public InstrumentType value() {
return type;
}
public static MusicInstrumentLiteral of(InstrumentType type) {
return new MusicInstrumentLiteral(type);
}
}
Finally, the next step is the class that will return the instrument based on the type by parameter; in this case, we will use the Orchestra
class. This class will inject all the Instrument
implementations visible by CDI, defined by Any
annotation, and based on the type, we will return the Instrument
instance.
The next step is to execute the code. For the sake of simplicity, we will use Java SE with CDI, but you can combine CDI with a rest application, a batch application, and so on.
public class App {
public static void main(String[] args) {
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
Orchestra orchestra = container.select(Orchestra.class).get();
Instrument keyboard = orchestra.select(InstrumentType.KEYBOARD);
Instrument string = orchestra.select(InstrumentType.STRING);
Instrument percussion = orchestra.select(InstrumentType.PERCURSSION);
System.out.println("Keyboard: " + keyboard.play());
System.out.println("String: " + string.play());
System.out.println("Percussion: " + percussion.play());
}
}
}
Conclusion
In this tutorial, you learned how to enhance traditional field injection in Jakarta CDI by leveraging its dynamic resolution capabilities. By combining polymorphism, custom qualifiers, and the API, you now have the knowledge to create flexible and extensible code that follows solid design principles, such as the Open/Closed Principle. Instead of relying on factories or conditional logic, you’ve seen how to allow CDI to resolve the appropriate implementation at runtime. This approach promotes cleaner, strategy-based, and plug-in-like architectures.
As a next step, consider applying this pattern in areas of your application where behavior varies based on runtime values, such as payment processors, export formats, or notification channels. Begin by identifying components that are interface-based and could benefit from extension without modification. Refactor them using the approach demonstrated here. This will help simplify your code and make it easier to test, extend, and maintain over time.
Video
Opinions expressed by DZone contributors are their own.
Comments