DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Introducing SmallRye LLM: Injecting Langchain4J AI Services
  • Introduction to Polymorphism With Database Engines in NoSQL Using Jakarta NoSQL
  • Implementation Best Practices: Microservice API With Spring Boot
  • Exploring Throttling in Java: Simple Implementation Examples - Part 1

Trending

  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  • Ethical AI in Agile
  • AI's Dilemma: When to Retrain and When to Unlearn?
  • From Zero to Production: Best Practices for Scaling LLMs in the Enterprise
  1. DZone
  2. Coding
  3. Java
  4. Injecting Implementations With Jakarta CDI Using Polymorphism

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.

By 
Otavio Santana user avatar
Otavio Santana
DZone Core CORE ·
Apr. 03, 25 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
5.1K Views

Join the DZone community and get the full member experience.

Join For Free

When 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:

Java
 
@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:

Java
 
@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:

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

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

Java
 
@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.

Java
 
@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.

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

Java
 
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


Implementation CDI Java (programming language) Polymorphism (computer science)

Opinions expressed by DZone contributors are their own.

Related

  • Introducing SmallRye LLM: Injecting Langchain4J AI Services
  • Introduction to Polymorphism With Database Engines in NoSQL Using Jakarta NoSQL
  • Implementation Best Practices: Microservice API With Spring Boot
  • Exploring Throttling in Java: Simple Implementation Examples - Part 1

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: