Introduction to Jakarta Contexts and Dependency Injection

We are working on a fresh, updated Jakarta EE Tutorial. This section hasn’t yet been updated.

This chapter describes Jakarta Contexts and Dependency Injection (CDI) which is one of several Jakarta EE features that help to knit together the web tier and the transactional tier of the Jakarta EE platform.

Getting Started

Contexts and Dependency Injection (CDI) enables your objects to have their dependencies provided to them automatically, instead of creating them or receiving them as parameters. CDI also manages the lifecycle of those dependencies for you.

For example, consider the following servlet:

@WebServlet("/cdiservlet")
public class NewServlet extends HttpServlet {
    private Message message;

    @Override
    public void init() {
        message = new MessageB();
    }

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
                  throws IOException {
        response.getWriter().write(message.get());
    }
}

This servlet needs an instance of an object that implements the Message interface:

public interface Message {
    public String get();
}

The servlet creates itself an instance of the following object:

public class MessageB implements Message {
    public MessageB() { }

    @Override
    public String get() {
        return "message B";
    }
}

Using CDI, this servlet can declare its dependency on a Message instance and have it injected automatically by the CDI runtime. The new servlet code is the following:

@WebServlet("/cdiservlet")
public class NewServlet extends HttpServlet {
    @Inject private Message message;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
                  throws IOException {
        response.getWriter().write(message.get());
    }
}

The CDI runtime looks for classes that implement the Message interface, finds the MessageB class, creates a new instance of it, and injects it into the servlet at runtime. To manage the lifecycle of the new instance, the CDI runtime needs to know what the scope of the instance should be. In this example, the servlet only needs the instance to process an HTTP request; the instance can then be garbage collected. This is specified using the jakarta.enterprise.context.RequestScoped annotation:

@RequestScoped
public class MessageB implements Message { ... }

For more information on scopes, see Using Scopes.

The MessageB class is a CDI bean. CDI beans are classes that CDI can instantiate, manage, and inject automatically to satisfy the dependencies of other objects. Almost any Java class can be managed and injected by CDI. For more information on beans, see About Beans. A JAR or WAR file that contains a CDI bean is a bean archive. For more information on packaging bean archives, see Configuring a CDI Application in this chapter and Packaging CDI Applications in Jakarta Contexts and Dependency Injection: Advanced Topics.

In this example, MessageB is the only class that implements the Message interface. If an application has more than one implementation of an interface, CDI provides mechanisms that you can use to select which implementation to inject. For more information, see Using Qualifiers in this chapter and Using Alternatives in CDI Applications in Jakarta Contexts and Dependency Injection: Advanced Topics.

Overview of CDI

CDI is a set of services that, used together, make it easy for developers to use enterprise beans along with Jakarta Faces technology in web applications. Designed for use with stateful objects, CDI also has many broader uses, allowing developers a great deal of flexibility to integrate various kinds of components in a loosely coupled but typesafe way.

CDI 3.0 is specified in a Jakarta EE specification. Related specifications that CDI uses include the following:

  • Jakarta Dependency Injection

  • The Managed Beans specification, an offshoot of the Jakarta EE platform specification

The most fundamental services provided by CDI are as follows.

  • Contexts: This service enables you to bind the lifecycle and interactions of stateful components to well-defined but extensible lifecycle contexts.

  • Dependency injection: This service enables you to inject components into an application in a typesafe way and to choose at deployment time which implementation of a particular interface to inject.

In addition, CDI provides the following services:

  • Integration with the Expression Language (EL), which allows any component to be used directly within a Jakarta Faces page or a Jakarta Server Pages page

  • The ability to decorate injected components

  • The ability to associate interceptors with components using typesafe interceptor bindings

  • An event-notification model

  • A web conversation scope in addition to the three standard scopes (request, session, and application) defined by the Jakarta Servlet specification

  • A complete Service Provider Interface (SPI) that allows third-party frameworks to integrate cleanly in the Jakarta EE environment

A major theme of CDI is loose coupling. CDI does the following:

  • Decouples the server and the client by means of well-defined types and qualifiers, so that the server implementation may vary

  • Decouples the lifecycles of collaborating components by

    • Making components contextual, with automatic lifecycle management

    • Allowing stateful components to interact like services, purely by message passing

  • Completely decouples message producers from consumers, by means of events

  • Decouples orthogonal concerns by means of Jakarta EE interceptors

Along with loose coupling, CDI provides strong typing by

  • Eliminating lookup using string-based names for wiring and correlations so that the compiler will detect typing errors

  • Allowing the use of declarative Java annotations to specify everything, largely eliminating the need for XML deployment descriptors, and making it easy to provide tools that introspect the code and understand the dependency structure at development time

About Beans

CDI redefines the concept of a bean beyond its use in other Java technologies, such as the JavaBeans and Jakarta Enterprise Beans technologies. In CDI, a bean is a source of contextual objects that define application state or logic. A Jakarta EE component is a bean if the lifecycle of its instances may be managed by the container according to the lifecycle context model defined in the CDI specification.

More specifically, a bean has the following attributes:

A bean type defines a client-visible type of the bean. Almost any Java type may be a bean type of a bean.

  • A bean type may be an interface, a concrete class, or an abstract class and may be declared final or have final methods.

  • A bean type may be a parameterized type with type parameters and type variables.

  • A bean type may be an array type. Two array types are considered identical only if the element type is identical.

  • A bean type may be a primitive type. Primitive types are considered to be identical to their corresponding wrapper types in java.lang.

  • A bean type may be a raw type.

About CDI Managed Beans

A managed bean is implemented by a Java class, which is called its bean class. A top-level Java class is a managed bean if it is defined to be a managed bean by any other Jakarta EE technology specification, such as the Jakarta Faces technology specification, or if it meets all the following conditions.

  • It is not a nonstatic inner class.

  • It is a concrete class or is annotated @Decorator.

  • It is not annotated with an enterprise bean component-defining annotation or declared as an enterprise bean class in ejb-jar.xml.

  • It has an appropriate constructor. That is, one of the following is the case.

    • The class has a constructor with no parameters.

    • The class declares a constructor annotated @Inject.

No special declaration, such as an annotation, is required to define a managed bean.

Beans as Injectable Objects

The concept of injection has been part of Java technology for some time. Since the Java EE 5 platform was introduced, annotations have made it possible to inject resources and some other kinds of objects into container-managed objects. CDI makes it possible to inject more kinds of objects and to inject them into objects that are not container-managed.

The following kinds of objects can be injected:

  • Almost any Java class

  • Session beans

  • Jakarta EE resources: data sources, Messaging topics, queues, connection factories, and the like

  • Persistence contexts (Jakarta Persistence EntityManager objects)

  • Producer fields

  • Objects returned by producer methods

  • Web service references

  • Remote enterprise bean references

For example, suppose that you create a simple Java class with a method that returns a string:

package greetings;

public class Greeting {
    public String greet(String name) {
        return "Hello, " + name + ".";
    }
}

This class becomes a bean that you can then inject into another class. This bean is not exposed to the EL in this form. Giving Beans EL Names explains how you can make a bean accessible to the EL.

Using Qualifiers

You can use qualifiers to provide various implementations of a particular bean type. A qualifier is an annotation that you apply to a bean. A qualifier type is a Java annotation defined as @Target({METHOD, FIELD, PARAMETER, TYPE}) and @Retention(RUNTIME).

For example, you could declare an @Informal qualifier type and apply it to another class that extends the Greeting class. To declare this qualifier type, use the following code:

package greetings;

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 java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

import jakarta.inject.Qualifier;

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Informal {}

You can then define a bean class that extends the Greeting class and uses this qualifier:

package greetings;

@Informal
public class InformalGreeting extends Greeting {
    public String greet(String name) {
        return "Hi, " + name + "!";
    }
}

Both implementations of the bean can now be used in the application.

If you define a bean with no qualifier, then the bean automatically has the qualifier @Default. The unannotated Greeting class could be declared as follows:

package greetings;

import jakarta.enterprise.inject.Default;

@Default
public class Greeting {
    public String greet(String name) {
        return "Hello, " + name + ".";
    }
}

Injecting Beans

To use the beans you create, you inject them into yet another bean that can then be used by an application, such as a Jakarta Faces application. For example, you might create a bean called Printer into which you would inject one of the Greeting beans:

import jakarta.inject.Inject;

public class Printer {

    @Inject Greeting greeting;
    ...
}

This code injects the @Default Greeting implementation into the bean. The following code injects the @Informal implementation:

import jakarta.inject.Inject;

public class Printer {

    @Inject @Informal Greeting greeting;
    ...
}

More is needed for the complete picture of this bean. Its use of scope needs to be understood. In addition, for a Jakarta Faces application, the bean needs to be accessible through the EL.

Now that you can identify the target of the injection, it is important to understand what can be injected and in what context. Faces 2.3 and above provides producers that enable most important Faces artifacts to be injected. For detailed information, see the package javadoc for jakarta.faces.annotation.

Using Scopes

For a web application to use a bean that injects another bean class, the bean needs to be able to hold state over the duration of the user’s interaction with the application. The way to define this state is to give the bean a scope. You can give an object any of the scopes described in Scopes, depending on how you are using it.

Scopes
Scope Annotation Duration

Request

@RequestScoped

A user’s interaction with a web application in a single HTTP request.

Session

@SessionScoped

A user’s interaction with a web application across multiple HTTP requests.

Application

@ApplicationScoped

Shared state across all users' interactions with a web application.

Dependent

@Dependent

The default scope if none is specified; it means that an object exists to serve exactly one client (bean) and has the same lifecycle as that client (bean).

Conversation

@ConversationScoped

A user’s interaction with a servlet, including Jakarta Faces applications. The conversation scope exists within developer-controlled boundaries that extend it across multiple requests for long-running conversations. All long-running conversations are scoped to a particular HTTP servlet session and may not cross session boundaries.

The first three scopes are defined by both Jakarta Context and Dependency Injection and the Jakarta Faces specification. The last two are defined by Jakarta Context and Dependency Injection.

All predefined scopes except @Dependent are contextual scopes. CDI places beans of contextual scope in the context whose lifecycle is defined by the Jakarta EE specifications. For example, a session context and its beans exist during the lifetime of an HTTP session. Injected references to the beans are contextually aware. The references always apply to the bean that is associated with the context for the thread that is making the reference. The CDI container ensures that the objects are created and injected at the correct time as determined by the scope that is specified for these objects.

You can also define and implement custom scopes, but that is an advanced topic. Custom scopes are likely to be used by those who implement and extend the CDI specification.

A scope gives an object a well-defined lifecycle context. A scoped object can be automatically created when it is needed and automatically destroyed when the context in which it was created ends. Moreover, its state is automatically shared by any clients that execute in the same context.

Jakarta EE components, such as servlets and enterprise beans, and JavaBeans components do not by definition have a well-defined scope. These components are one of the following:

  • Singletons, such as enterprise singleton beans, whose state is shared among all clients

  • Stateless objects, such as servlets and stateless session beans, which do not contain client-visible state

  • Objects that must be explicitly created and destroyed by their client, such as JavaBeans components and stateful session beans, whose state is shared by explicit reference passing between clients

However, if you create a Jakarta EE component that is a managed bean, then it becomes a scoped object, which exists in a well-defined lifecycle context.

The web application for the Printer bean will use a simple request and response mechanism, so the managed bean can be annotated as follows:

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;

@RequestScoped
public class Printer {

    @Inject @Informal Greeting greeting;
    ...
}

Beans that use session, application, or conversation scope must be serializable, but beans that use request scope do not have to be serializable.

Giving Beans EL Names

To make a bean accessible through the EL, use the @Named built-in qualifier:

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named
@RequestScoped
public class Printer {

    @Inject @Informal Greeting greeting;
    ...
}

The @Named qualifier allows you to access the bean by using the bean name, with the first letter in lowercase. For example, a Facelets page would refer to the bean as printer.

You can specify an argument to the @Named qualifier to use a nondefault name:

@Named("MyPrinter")

With this annotation, the Facelets page would refer to the bean as MyPrinter.

Adding Setter and Getter Methods

To make the state of the managed bean accessible, add setter and getter methods for that state. The createSalutation method calls the bean’s greet method, and the getSalutation method retrieves the result.

Once the setter and getter methods have been added, the bean is complete. The final code looks like this:

package greetings;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named
@RequestScoped
public class Printer {

    @Inject @Informal Greeting greeting;

    private String name;
    private String salutation;

    public void createSalutation() {
        this.salutation = greeting.greet(name);
    }

    public String getSalutation() {
        return salutation;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Using a Managed Bean in a Facelets Page

To use the managed bean in a Facelets page, create a form that uses user interface elements to call its methods and to display their results. The following example provides a button that asks the user to type a name, retrieves the salutation, and then displays the text in a paragraph below the button:

<h:form id="greetme">
   <p><h:outputLabel value="Enter your name: " for="name"/>
      <h:inputText id="name" value="#{printer.name}"/></p>
   <p><h:commandButton value="Say Hello"
                       action="#{printer.createSalutation}"/></p>
   <p><h:outputText value="#{printer.salutation}"/></p>
</h:form>

Injecting Objects by Using Producer Methods

Producer methods provide a way to inject objects that are not beans, objects whose values may vary at runtime, and objects that require custom initialization. For example, if you want to initialize a numeric value defined by a qualifier named @MaxNumber, then you can define the value in a managed bean and then define a producer method, getMaxNumber, for it:

private int maxNumber = 100;
...
@Produces @MaxNumber int getMaxNumber() {
    return maxNumber;
}

When you inject the object in another managed bean, the container automatically invokes the producer method, initializing the value to 100:

@Inject @MaxNumber private int maxNumber;

If the value can vary at runtime, then the process is slightly different. For example, the following code defines a producer method that generates a random number defined by a qualifier called @Random:

private java.util.Random random =
    new java.util.Random( System.currentTimeMillis() );

java.util.Random getRandom() {
        return random;
}

@Produces @Random int next() {
    return getRandom().nextInt(maxNumber);
}

When you inject this object in another managed bean, you declare a contextual instance of the object:

@Inject @Random Instance<Integer> randomInt;

You then call the get method of the Instance:

this.number = randomInt.get();

Configuring a CDI Application

When your beans are annotated with a scope type, the server recognizes the application as a bean archive and no additional configuration is required. The possible scope types for CDI beans are listed in Using Scopes.

CDI uses an optional deployment descriptor named beans.xml. Like other Jakarta EE deployment descriptors, the configuration settings in beans.xml are used in addition to annotation settings in CDI classes. The settings in beans.xml override the annotation settings if there is a conflict. An archive must contain the beans.xml deployment descriptor only in certain limited situations, described in Jakarta Contexts and Dependency Injection: Advanced Topics.

For a web application, the beans.xml deployment descriptor, if present, must be in the WEB-INF directory. For EJB modules or JAR files, the beans.xml deployment descriptor, if present, must be in the META-INF directory.

Using the @PostConstruct and @PreDestroy Annotations with CDI Managed Bean Classes

CDI managed bean classes and their superclasses support the annotations for initializing and for preparing for the destruction of a bean. These annotations are defined in Jakarta Annotations (https://jakarta.ee/specifications/annotations/2.0/).

To Initialize a Managed Bean Using the @PostConstruct Annotation

Initializing a managed bean specifies the lifecycle callback method that the CDI framework should call after dependency injection but before the class is put into service.

  1. In the managed bean class or any of its superclasses, define a method that performs the initialization that you require.

  2. Annotate the declaration of the method with the jakarta.annotation.PostConstruct annotation.

When the managed bean is injected into a component, CDI calls the method after all injection has occurred and after all initializers have been called.

As mandated in Jakarta Annotations, if the annotated method is declared in a superclass, the method is called unless a subclass of the declaring class overrides the method.

The UserNumberBean managed bean in The guessnumber-cdi CDI Example uses @PostConstruct to annotate a method that resets all bean fields:

@PostConstruct
public void reset () {
    this.minimum = 0;
    this.userNumber = 0;
    this.remainingGuesses = 0;
    this.maximum = maxNumber;
    this.number = randomInt.get();
}

To Prepare for the Destruction of a Managed Bean Using the @PreDestroy Annotation

Preparing for the destruction of a managed bean specifies the lifecycle call back method that signals that an application component is about to be destroyed by the container.

  1. In the managed bean class or any of its superclasses, prepare for the destruction of the managed bean.

    In this method, perform any cleanup that is required before the bean is destroyed, such as releasing a resource that the bean has been holding.

  2. Annotate the declaration of the method with the jakarta.annotation.PreDestroy annotation.

CDI calls this method before starting to destroy the bean.

Further Information about CDI

For more information about CDI, see