Getting Started With Jakarta EE 10 - Jakarta CDI

Photo of Luqman Saeed by Luqman Saeed

Jakarta EE 10, the first major release of the platform since it was transferred to the Eclipse Foundation, did come with a slew of changes and updates to many of its constituent specifications. One such specification that received updates is the Jakarta Contexts and Dependency Injection specification. The specification release version for Jakarta EE 10 is Jakarta CDI 4.0, which came with major changes.

Two of such major changes are the split of the specification into a Lite and Full profiles and the change in default behaviour for an empty beans.xml file. In this blog post, we take a quick look at getting started with Jakarta CDI, in my view, the single most influential specification on the Jakarta EE Platform.

Setup

As a coreJakarta EEspecification, there’s not much setup to be done in getting Jakarta CDI up and running. Your typical Jakarta EE application will already contain a dependency on the platform in your dependency file, almost certainly the Maven pom.xml file. If you are not sure where or how to get started, check outmy very opinionated guide to getting started with Jakarta EE 10. 

With the platform dependency in place, we need to configure CDI. The sample beans.xml file below shows how to set the bean discovery mode to  all. 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
bean-discovery-mode="all">

</beans>

You might be asking what is the bean discovery mode in the first place? Think of it as a way to tell the dependency injection container which of your Java classes, or beans, it should “index” and make available on request. As Jakarta CDI is at its core a dependency injection implementation, you can determine explicitly which of your beans should be managed by the container. The bean discovery mode has three possible values, namely annotated, all and none.

Annotated means you want only beans that are explicitly annotated with Jakarta CDI annotations to be managed on your behalf. All means all Java beans in your application should be managed and none effectively disables CDI. In Jakarta CDI 4.0, shipped with Jakarta EE 10, an empty beans.xml file is now translated equivalent to setting a bean discovery mode of annotation. You should avoid setting it to none. I recommend using all at all times.

Features of Jakarta CDI

Jakarta CDI is a IoC inversion of control that not only helps you write powerful, loosely coupled code, but also has features that help manage application complexity in a much simplified way. These features together, allow you to break your applicaiton down into components that can be swapped out dynamically at runtime with minimal to no application rewrite or even redeployment. Five core features you get with Jakarta CDI are:

Typesafe Dependency Resolution Mechanism

CDI uses Java types to resolve dependencies. Unlike other dependency injection implementations that are string based, CDI uses Java types through a combination of metadata and Java beans to resolve dependencies that you declare. This is achieved through a sophisticated resolution mechanism that allows you to select dependencies at development or deployment time. 

Well Defined Lifecycle Contexts

CDI allows you to bind stateful objects to a well defined, extensible lifecycle context. Each instance of a bean that you request from the runtime has a predictable lifecycle, complete with callbacks that you can hook into. 

Integration With the Web Tier

Through integration with the Jakarta Unified Expression Language (EL), contextual objects like Jakarta Enterprise Beans can be accessed directly from a Jakarta Server Faces page.

Interceptors

Jakarta CDI interceptors allow you to associate interceptors with Java objects through typesafe interceptor bindings. You can use these interceptors to implement orthogonal services like logging in your applications.

Events

Jakarta CDI events are a powerful, typesafe, fully decoupled communication mechanism between different parts of your application. One component can fire an event, and any registered listeners of that event will be notified or called by the CDI runtime, passing in the requisite event payload automatically.

Beans And Scopes

But what is a bean? What makes a Java class qualify to be managed by Jakarta CDI? A bean in CDI is any Java class that has either a no-arg constructor or at least one injectable constructor. It can optionally be explicitly annotated with one or more CDI annotations. 

A bean however, cannot be an inner or abstract class. A CDI bean isn’t very different from a JavaBean. In fact, any JavaBean is automatically a CDI bean. Technically, this kind of bean is called a managed bean. Managed in the sense that the creation and destruction of such bean instances are fully managed by the CDI container.

With your knowledge of what a bean is, you effectively know the core of CDI. It is a way to make beans available to dependents in an automatic, loosely coupled way. Take the following sample controller for instance. 


@ApplicationScoped
public class DataController {
    
    @Inject
    private Datastore datastore;

}
The DataController is a simple Java class annotated with @ApplicationScoped. This is a scope in CDI. Scopes refer to carefully defined lifetimes within which a given bean can exist and will be destroyed once the associated scope ceases to exist. The DataController shown above is placed in the application scope. This scope exists from application boot to shutdown. Effectively @ApplicationScoped annotations create singletons. The other available scopes are @RequestScoped, @SessionScoped and @Dependent. 

The @Inject annotation signals to the CDI container to inject an instance of the annotated type, in this case DataStore into the declared field variable. Technically, the instance created by the CDI container and injected into the field is a contextual instance. But you will hardly need to know that technicality. 

Even though the above sample class and its metadata look very underwhelming, there is a lot going on there. The DataController only declares its dependency on the Datastore and leaves the management of such dependency to the CDI container. It does not concern itself with the creation of its dependency. It delegates that to the container, which manages the DataController instance and its dependency. The Datastore could be in a different scope and that would still be managed on your behalf.

Producers

One of the powerful features of Jakarta CDI is that it allows you to even transform Java classes that are not part of your project code into CDI managed beans. The DataController above depends on the Datastore, which is injected into it. However, the Datastore is from a third party library that our project depends on. It’s not part of our application code at all. However, as you can see above, we are still requesting it much like we would our own bean. Remember the requirements of a CDI bean? Since the Datastore is not part of our application code, it may not meet that requirement. 

However, through the Jakarta CDI producer construct, we are able to transform the Datastore, a completely external class into a CDI managed bean. The code snippet below shows us “producing” Datastore instances using the CDI producer method.


    @Produces
    @ApplicationScoped
    public Datastore generateDatastore() {
        var datastore = Morphia.createDatastore(MongoClients.create(connectionString), databaseName);
        datastore.getMapper().mapPackage(HelloEntity.class.getPackageName());

        if (DeploymentStage.DEV.name().equalsIgnoreCase(deploymentStage)) {
            datastore.getDatabase().drop();
        }

        datastore.ensureIndexes();
        return datastore;
    }
A CDI producer method is a method annotated @jakarta.enterprise.inject.Produces, optionally with a scope. The method is said to produce the type it returns. In the above snippet, the method returns Datastore, and within it, we use the API of the third party library to create and instance that is then returned. When the CDI runtime encounters a field of type Datastore annotated with @Inject, as we have in the DataController, it “knows” to call this method for an instance. As the method is annotated @ApplicationScoped, the returned instance will be associated with that scope, effectively creating a singleton of the Datastore. 

There is a lot more to the Jakarta CDI specification. As the Jakarta EE Platform keeps evolving, CDI will increasingly become the underlying “glue” that holds all the disparate parts of the platform into a cohesive whole. Take a look at the specification document here. Don’t worry, it’s an easy read at just about 200 pages. You can also check out this guide we have that goes a lot more into details about what CDI is and how to use it. 
 
Try these other resources to find out more:

Comments