BackendBench
Coffee

Unravel › Java concepts › Jakarta and other frameworks

Spring

CDI: contexts and dependency injection (Jakarta EE)

CDI is the standard dependency injection of Jakarta EE (and Quarkus).

🔎 Unravel this example

At a glance

Mental model

Spring is a kitchen manager who stocks stations.

What happens, in order
  1. 1@Inject on a field, constructor or setter requests a bean by typeQualifiers (@Named, custom annotations) pick among several implementations; @Alternative plus @Priority swaps one globally.
  2. 2Scopes: @ApplicationScoped (one shared instance), @RequestScoped (per HTTP request), @SessionScoped (per user session), @Dependent (new instance per injection point, the default)
  3. 3Events: Event<OrderPlaced>.fire(event) publishes, void on(@Observes OrderPlaced e) receivesObservers can be transactional (@Observes(during = AFTER_SUCCESS)) and async (@ObservesAsync).
  4. 4Producers (@Produces) turn the result of a method or field into a bean, the CDI counterpart of Spring @BeanInterceptors (@InterceptorBinding) add cross-cutting behaviour like Spring AOP.
Remember
Watch out
Mixing javax and jakarta importsFinal classes or missing no-arg constructor for proxied scopesAssuming @Dependent shares one instanceAmbiguous injection points without qualifiers
Example (highlighted lines explain it)
@ApplicationScopedpublic class OrderNotifier {    @Inject    MailService mail;    void onOrderPlaced(@Observes OrderPlaced event) {        mail.sendConfirmation(event.customerEmail());    }}@RequestScopedpublic class Checkout {    @Inject    Event<OrderPlaced> placed;    public void complete(Order order) {        placed.fire(new OrderPlaced(order.id(), order.customerEmail()));    }}
  1. line 1 uses ApplicationScoped, the core of CDI: contexts and dependency injection (Jakarta EE)
  2. line 6 uses Observes, the core of CDI: contexts and dependency injection (Jakarta EE)
  3. line 11 uses RequestScoped, the core of CDI: contexts and dependency injection (Jakarta EE)
Read the full explanation (about 2 min)

CDI is the standard dependency injection of Jakarta EE (and Quarkus). Like Spring beans, classes get what they need injected, but each bean has a scope that says how long it lives, and components talk through typed events.

A mental model

Spring is a kitchen manager who stocks stations. CDI is the same job done by a union rulebook: every item carries a label saying how long it stays on the line (per request, per session, forever) and the cooks announce events on a loudspeaker.

How it works

@Inject on a field, constructor or setter requests a bean by type. Qualifiers (@Named, custom annotations) pick among several implementations; @Alternative plus @Priority swaps one globally.

Scopes: @ApplicationScoped (one shared instance), @RequestScoped (per HTTP request), @SessionScoped (per user session), @Dependent (new instance per injection point, the default).

Events: Event<OrderPlaced>.fire(event) publishes, void on(@Observes OrderPlaced e) receives. Observers can be transactional (@Observes(during = AFTER_SUCCESS)) and async (@ObservesAsync).

Producers (@Produces) turn the result of a method or field into a bean, the CDI counterpart of Spring @Bean. Interceptors (@InterceptorBinding) add cross-cutting behaviour like Spring AOP.

Going deeper

Normal-scoped beans (application, request, session) are injected as client proxies, so the injected object is a stand-in that finds the real instance of the current scope on each call. Beans must therefore be non-final and have a no-argument constructor.

Injecting a @RequestScoped bean into an @ApplicationScoped one is safe because of the proxy; injecting a @Dependent one gives a single instance for the lifetime of the host bean, which surprises people.

Package names: javax.inject and javax.enterprise became jakarta.inject and jakarta.enterprise in Jakarta EE 9. The old and new must not mix on one classpath.

Under the hood

At startup the container discovers beans by scanning archives (annotated bean classes with bean-discovery-mode), builds a resolution table by type and qualifiers, and fails the deployment if an injection point has zero or several matches. Quarkus does this at build time (ArC) so no reflection is needed at runtime.

Common mistakes

  • Mixing javax and jakarta imports.
  • Final classes or missing no-arg constructor for proxied scopes.
  • Assuming @Dependent shares one instance.
  • Ambiguous injection points without qualifiers.

How Unravel recognises it

When you paste code, Unravel looks for these signs of cdi: contexts and dependency injection (jakarta ee). Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Builds on this

Frequently asked questions

What is CDI: contexts and dependency injection (Jakarta EE)?

CDI is the standard dependency injection of Jakarta EE (and Quarkus). Like Spring beans, classes get what they need injected, but each bean has a scope that says how long it lives, and components talk through typed events.

What are the most common mistakes with CDI: contexts and dependency injection (Jakarta EE)?

Mixing javax and jakarta imports. Final classes or missing no-arg constructor for proxied scopes. Assuming @Dependent shares one instance. Ambiguous injection points without qualifiers.

How can I see CDI: contexts and dependency injection (Jakarta EE) in real code?

Paste your own Java into Unravel and it will point out where cdi: contexts and dependency injection (jakarta ee) appears, explain how it works at the detail level you choose, and draw the flow of the code around it.