Java Module 25 – Jakarta REST (JAX-RS) and CDI: services beyond Faces
CDI qualifiers, producers and alternatives
Choose between beans of one type with CDI qualifiers, create beans from code with producer methods, and swap implementations with @Alternative and @Priority.
What you will learn
- Choose between several implementations of one type with qualifiers and Instance<T>
- Create beans from code with producer methods and fields, and clean them up with disposer methods
- Swap implementations for tests and environments with @Alternative and @Priority
Before you start
What you’ll learn · Before you start
What you will learn
- Choose between several implementations of one type with qualifiers and Instance<T>
- Create beans from code with producer methods and fields, and clean them up with disposer methods
- Swap implementations for tests and environments with @Alternative and @Priority
Before you start
On this page
When two beans share a type, an injection point that asks only for the type is ambiguous, and the CDI container
refuses to start. CDI qualifiers settle it: a qualifier is an annotation you put on a bean and on the injection points
that want that particular bean. Producers cover a second gap, objects the container cannot build with a constructor,
such as a java.time.Clock or a thread pool: a producer method returns them, and a disposer method cleans them up.
Alternatives cover a third case, replacing a bean everywhere for a test or an environment without editing the code
that injects it: @Alternative marks the stand-in, and @Priority switches it on for the whole application. The
runs below use Weld 6.0.4.Final for CDI 4.1 and fictional payment gateways, and their output is recorded.
When one type has two beans
The container picks a bean for an injection point by its type and its qualifiers. A bean that declares no qualifier
has @Default, an injection point without one asks for @Default, and every bean also has @Any. Checkout asks
for two gateways, one plain and one with the @Upi qualifier, and Resolution starts it in three containers:
package com.example.payments;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;
@Dependent
public class Checkout {
@Inject // no qualifier: the bean with @Default
PaymentGateway byDefault;
@Inject
@Upi // the bean with @Upi
PaymentGateway instant;
public String payBoth(String order) {
return byDefault.pay(order, 1_450) + "; " + instant.pay(order, 320);
}
} from session import run
# Compiles the project, asks Maven for the class path of Weld SE, then runs Resolution.
run(
"""
cd payments
mvn -q compile dependency:build-classpath -Dmdep.outputFile=cp.txt
java -cp target/classes:$(cat cp.txt) com.example.payments.Resolution
"""
) Output
# openjdk version "25.0.4.1" LTS · Apache Maven 3.10.0
$ cd payments
$ mvn -q compile dependency:build-classpath -Dmdep.outputFile=cp.txt
$ java -cp target/classes:$(cat cp.txt) com.example.payments.Resolution
card, wallet and upi:
the container did not start: WELD-001409: Ambiguous dependencies for type PaymentGateway with qualifiers @Default
at injection point [BackedAnnotatedField] @Inject com.example.payments.Checkout.byDefault
at com.example.payments.Checkout.byDefault(Checkout.java:0)
Possible dependencies:
- Managed Bean [class com.example.payments.CardGateway] with qualifiers [@Any @Default]
- Managed Bean [class com.example.payments.WalletGateway] with qualifiers [@Any @Default]
upi only:
the container did not start: WELD-001408: Unsatisfied dependencies for type PaymentGateway with qualifiers @Default
at injection point [BackedAnnotatedField] @Inject com.example.payments.Checkout.byDefault
at com.example.payments.Checkout.byDefault(Checkout.java:0)
WELD-001475: The following beans match by type, but none have matching qualifiers:
- Managed Bean [class com.example.payments.UpiGateway] with qualifiers [@Upi @Any]
card and upi:
card took 1450 rupees for order 1; upi took 320 rupees for order 1
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 resolution_session.py
With the card and wallet gateways both @Default, the start failed with WELD-001409: Ambiguous dependencies and
listed both candidates. With only the qualified gateway present, it failed with WELD-001408: Unsatisfied dependencies, and Weld added WELD-001475, which says that a bean matched the type but not the qualifiers, a common
cause of this error. Both messages named the injection point, Checkout.byDefault, and both stopped the start-up
before any payment code ran. With one gateway of each kind, each field received its own bean.
Qualifiers, producers and alternatives at a glance
How the container chooses the bean for an injection point
Text description of the diagram
A flow from top to bottom.
1. "An injection point: its type and qualifiers (none written: @Default)" leads to 2. "Every enabled bean of that type: classes, producer methods, fields", which leads to 3. "Keep the beans with every qualifier asked for". From there, three arrows: - to "None left: unsatisfied, WELD-001408"; - to "One left: injected"; - to "Several left: alternatives only, then top @Priority". From that box, an arrow labelled "one remains" leads to "One left: injected", and another leads to "Still several: ambiguous, WELD-001409".
The container does this for every injection point when the application starts, so the two errors stop the start-up instead of a request.
| Tool | What it answers | Where it is written | Typical use |
|---|---|---|---|
| a qualifier | which of several beans of one type? | an annotation on the bean and at the injection point | two gateways, two late-fee rules |
Instance<T> |
which bean, chosen while the program runs? | code that calls select() and get() |
the payment method a customer picks |
| a producer method or field | how is this object made? | a method or field annotated @Produces |
a Clock, a thread pool, a configuration value |
| a disposer method | how is a produced object cleaned up? | a method with a @Disposes parameter |
closing the pool when the container stops |
an alternative with @Priority |
which bean replaces the usual one? | @Alternative and @Priority on the stand-in |
a fake gateway in tests, a sandbox in staging |
A qualifier labels one bean of a type so that an injection point can ask for it by that label. @Named is a
built-in qualifier that labels a bean with a string, mainly for Expression Language. Instance<T> looks beans up
while the program runs, so the choice can depend on data. A producer method is a method whose return value becomes a
bean. A producer field does the same with the value of a field. A disposer method receives a produced object when its
context ends, to close it. An alternative is a bean that stays switched off until it is selected. @Priority
selects an alternative for the whole application, and the highest priority wins. CDI Full can also select
alternatives for one archive in beans.xml. Producers and alternatives take part in the same resolution as every
other bean, so their mistakes, too, stop the start-up rather than a request.
A qualifier for the second gateway
A qualifier is an annotation type annotated @Qualifier and kept at run time. Putting it on UpiGateway takes away
that bean’s @Default, so only injection points that ask for @Upi receive it:
payments/src/main/java/com/example/payments/Upi.java
package com.example.payments;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/** A qualifier: an annotation that labels one bean of a type, and the injection points that want that bean. */
@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD, PARAMETER })
public @interface Upi {
/** An instance of the annotation for code that selects beans at run time: new Upi.Literal(). */
final class Literal extends AnnotationLiteral<Upi> implements Upi {
private static final long serialVersionUID = 1L;
}
} payments/src/main/java/com/example/payments/UpiGateway.java
package com.example.payments;
import jakarta.enterprise.context.ApplicationScoped;
/** Qualified @Upi, so it no longer has @Default: an injection point without a qualifier never receives it. */
@Upi
@ApplicationScoped
public class UpiGateway implements PaymentGateway {
@Override
public String name() {
return "upi";
}
} payments/src/main/java/com/example/payments/CardGateway.java
package com.example.payments;
import jakarta.enterprise.context.ApplicationScoped;
/** No qualifier declared, so it has @Default (and @Any, which every bean has). */
@ApplicationScoped
public class CardGateway implements PaymentGateway {
@Override
public String name() {
return "card";
}
} The nested Literal class creates instances of the annotation that code can pass around, for the run-time choices
of Instance<T>. CDI qualifiers are Java types, so a typing mistake in one is a compile error, while a mistyped
@Named string is found only when the container or a page looks the name up; in Java code, prefer qualifiers.
India-specific: UPI
UPI, the Unified Payments Interface run by the National Payments Corporation of India, moves money between bank
accounts in real time from a phone. In this lesson UpiGateway is fictional and contacts no payment network; it only
stands for “a second way to pay” next to cards.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Choosing at run time and making beans from code Pro: opens with a Pro, Premium or Ultimate pass
Some choices can only be made while the program runs, and some objects cannot be built by the container at all. This
part shows both, with Instance<T> and producers, and reproduces two of their failures with numbers.
In this part:
- Choosing a gateway at run time with Instance and annotation literals
- Dependent objects that Instance keeps until you destroy them, measured
- Producer methods and fields for objects CDI cannot construct
- Nulls, Optional and the scope of a producer, reproduced
- Disposer methods that close what producers opened
6 runnable examples (ClockProducer.java, Java, LateFeePolicy.java, Python, ReminderPool.java) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Swapping implementations for tests and environments Pro: opens with a Pro, Premium or Ultimate pass
An alternative replaces a bean without touching the code that injects it. This part shows which alternative wins when several are enabled, gives a test a fixed clock with an alternative producer, and ends with an exercise.
In this part:
- Replacing the default gateway with @Alternative and @Priority, recorded
- Which alternative wins: priorities, ties and selection compared
- A fixed clock for tests, with an alternative producer
- Qualifier, alternative, producer or Instance: the trade-offs
- Your turn: a capped late fee and a test clock
6 runnable examples (CappedLateFee.java, Clocks.java, FixedClocks.java, Java, LateFeePolicyTest.java, Python, ReturnDesk.java, ReturnDeskTest.java) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher to mid · Warm-up
What do the @Default and @Any qualifiers mean in CDI?
Every bean has
@Any, whatever else it declares, so an injection point or a lookup with@Anycan reach every bean of a type.@Defaultis the qualifier a bean gets when it declares no qualifier of its own other than@Namedor@Any, and it is also what an injection point asks for when it names no qualifier. So a plain class such asCardGatewayhas@Anyand@Default, whileUpiGateway, annotated@Upi, has@Upiand@Anybut no@Default, and an injection point without a qualifier never receives it.@Anyis mostly used withInstance<T>:@Inject @Any Instance<PaymentGateway>reaches every gateway, andselect()narrows the choice at run time. Writing@Defaultyourself is rarely needed, but it is allowed, for example to keep a bean the default while it also carries another qualifier. -
Mid to senior
A second team adds another PaymentGateway implementation, and the next release fails at start-up with "WELD-001409: Ambiguous dependencies" at forty injection points. How do you fix it without editing every injection point, and how do you stop it happening again?
-
Mid to senior
An @ApplicationScoped service calls instance.get() on a @Dependent formatter for every request. After three days the heap is full of formatter objects, although the code keeps no reference to them. Explain the cause and fix it.
-
Fresher to mid
A CDI application charges late fees that depend on today's date, and its tests fail around midnight and at month ends. How do you make "today" controllable in tests without adding test-only branches to production code?
Key takeaways
- Two beans of one type make an unqualified injection point ambiguous, and the container refuses to start.
- A qualifier labels a bean and the injection points that want it; a bean without one has
@Default, and every bean has@Any. - Producer methods and fields make beans of objects CDI cannot construct, and disposer methods clean them up.
- An alternative replaces a bean everywhere once
@Priorityselects it; the highest priority wins. - Resolution errors stop the start-up, so wiring mistakes never wait for a user to find them.
References
- Jakarta Contexts and Dependency Injection 4.1 specification (Eclipse Foundation)
- Jakarta Contexts and Dependency Injection 4.1 (Eclipse Foundation)
- Jakarta CDI 4.1 API documentation (Eclipse Foundation)
- Weld 6.0.4.Final reference: application servers and environments (Weld project)
- Weld: the CDI reference implementation (Weld project)
- Clock (Java SE 25 API) (Oracle)
- UPI: Unified Payments Interface (National Payments Corporation of India)
Report a problem with this lesson
Kept only in this browser. Your Learn progress