Getting Start with Weld, the CDI Reference Implementation

Weld, the CDI Compatible Implementation, can be downloaded from the download page. Information about the Weld source code repository and instructions about how to obtain and build the source can be found on the same page.

Weld provides a complete SPI allowing Jakarta EE containers such as WildFly, GlassFish and WebLogic to use Weld as their built-in CDI implementation. Weld also runs in servlet engines like Tomcat and Jetty, or even in a plain Java SE environment.

Weld comes with several examples showing various possible usages:

  • A full blow Jakarta EE server (WildFly)

  • Servlets such as Tomcat or Jetty

  • Standalone Java SE application

Many more Jakarta EE examples can be seen in quickstarts of Jakarta EE servers. A good repository to browse would be WildFly Quickstarts as it shows many more Jakarta-world technologies smoothly integrating with CDI/Weld.

Getting started with Weld

Weld comes with several numberguess examples in various flavors based on what environment you use. In its classic variant, it is a web (war) example containing only non-transactional managed beans. This example can be run on a wide range of servers, including WildFly, GlassFish, Apache Tomcat, Jetty, and any compliant Jakarta EE container.

The example uses JSF as the web framework and, as such, can be found in the examples/jsf directory of the Weld distribution.

Prerequisites

To run the examples with the provided build scripts, you’ll need the following:

  • the latest release of Weld, which contains the examples

  • Maven 3, to build and deploy the examples

  • optionally, a supported runtime environment (minimum versions shown)

    • WildFly,

    • GlassFish,

    • Apache Tomcat, or

    • Jetty

Note that the version of these runtimes need to target the same Jakarta EE version that Weld does.

In the next few sections, you’ll be using the Maven command (mvn) to invoke the Maven project file in each example to compile, assemble and deploy the example to WildFly and, for the war example, Apache Tomcat. You can also deploy the generated artifact (war) to any other container that supports Jakarta EE, such as GlassFish.

The sections below cover the steps for deploying with Maven in detail.

First try

If you simply want to run the numberguess example without the requirement of a specific runtime you can start with the following commands:

$> cd examples/jsf/numberguess
$> mvn wildfly:run

The Maven WildFly plugin will run WildFly and deploy the example and the server will be automatically downloaded in the target directory. The numberguess application is available at http://localhost:8080/weld-numberguess.

Deploying to WildFly

To deploy the examples to your own WildFly instance, you’ll need to download WildFly first. The good news is that there are no additional modifications you have to make to the server. It’s ready to go!

After you have downloaded WildFly, extract it. You can move the extracted folder anywhere you like. Wherever it lays to rest, that’s what we’ll call the WildFly installation directory, or JBOSS_HOME.

$> unzip wildfly-31.x.y.Final.zip
$> mv wildfly-31.*/ wildfly-31

In order for the build scripts to know where to deploy the example, you have to tell them where to find your WildFly installation. Set the JBOSS_HOME environment variable to point to the WildFly installation, e.g.:

$> export JBOSS_HOME=/path/to/wildfly-31

Next up, start your WildFly server. Assuming default configuration and Linux, you can do that with the following command (for Windows, use the .bat file instead):

$> cd path/to/wildfly
$> ./bin/standalone.sh

You’re now ready to run your first example!

Switch to the examples/jsf/numberguess directory in Weld repository and execute the Maven deploy target:

$> cd examples/jsf/numberguess
$> mvn wildfly:deploy

Wait a bit for the application to deploy and see if you can determine the most efficient approach to pinpoint the random number at the local URL http://localhost:8080/weld-numberguess.

Note

The Maven WildFly plugin includes additional goals for WildFly to deploy and undeploy the archive.

  • mvn wildfly:deploy - deploy the example to a running WildFly instance

  • mvn wildfly:undeploy - undeploy the example from a running WildFly instance

  • mvn wildfly:redeploy - redeploys the example

For more information on the WildFly Maven plugin see the plugin documentation.

TODO this ain’t true!!!

You can also run some simple integration tests to verify that the example works as expected. Keep the server with deployed application running and execute the following:

$> mvn verify -Pintegration-testing

You should see the following output:

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Deploying to Apache Tomcat

Servlet containers are not required to support Jakarta EE services like CDI. However, you can use CDI in a servlet container like Tomcat by embedding a standalone CDI implementation such as Weld.

Weld comes with servlet integration extension which bootstraps the CDI environment and provides injection into servlets components. Basically, it emulates some of the work done by the Jakarta EE container, but you don’t get the enterprise features such as session beans and container-managed transactions.

Note
Note that due to limitations of servlet containers (e.g. read-only JNDI) your application might require some additional configuration as well (see Tomcat and Jetty for more info).

Let’s give the Weld servlet extension a spin on Apache Tomcat. First, you’ll need to download Tomcat 10.1 or later from tomcat.apache.org and extract it.

$> unzip apache-tomcat-10.1.x.zip

The Maven plugin communicates with Tomcat over HTTP, so it doesn’t care where you have installed Tomcat. However, the plugin configuration assumes you are running Tomcat in its default configuration, with a hostname of localhost and port 8080. The readme.txt file in the example directory has information about how to modify the Maven settings to accommodate a different setup.

You can either start Tomcat from a Linux shell:

$> cd /path/to/apache-tomcat-10.1
$> ./bin/startup.sh

a Windows command window:

$> cd c:\path\to\apache-tomcat-10\bin
$> start

or you can start the server using an IDE, like Eclipse.

Change to the examples/jsf/numberguess directory again and run the following Maven command:

$> cd examples/jsf/numberguess
$> mvn clean package -Ptomcat

Now you’re ready to deploy the numberguess example to Tomcat!

$> cp examples/jsf/numberguess/target/weld-numberguess.war apache-tomcat/webapps/

Diving into the Weld examples

It’s time to pull the covers back and dive into the internals of Weld example applications. Let’s start with the simpler of the two examples, weld-numberguess.

The numberguess example in depth

In the numberguess application you get 10 attempts to guess a number between 1 and 100. After each attempt, you’re told whether your guess was too high or too low.

The numberguess example is comprised of a number of beans, configuration files and Facelets (JSF) views, packaged as a war module. Let’s start by examining the configuration files.

All the configuration files for this example are located in WEB-INF/, which can be found in the src/main/webapp directory of the example. First, we have the JSF 4.0 version of faces-config.xml. A standardized version of Facelets is the default view handler in JSF, so there’s really nothing that we have to configure. Thus, the configuration consists of only the root element.

<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="
                  https://jakarta.ee/xml/ns/jakartaee
                  https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd"
              version="4.0">
</faces-config>

There’s also an empty beans.xml file, which tells the container to look for beans in this archive and to activate the CDI services.

Finally, some supported servers also need a web.xml which is located in src/main/webapp-[server]/WEB-INF.

Note
This demo uses JSF as the view framework, but you can use Weld with any servlet-based web framework.

TODO continue here

Let’s take a look at the main JSF view, src/main/webapp/home.xhtml.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="jakarta.faces.facelets"
      xmlns:h="jakarta.faces.html"
      xmlns:f="jakarta.faces.core">

<head>
   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
   <title>numberguess</title>
</head>

<body>
<div id="content">
   <h1>Guess a number...</h1>
   <h:form id="numberGuess">

      <!-- Feedback for the user on their guess -->
      <div style="color: red">
         <h:messages id="messages" globalOnly="false" /><!--(1)-->
         <h:outputText id="Higher" value="Higher!"
                       rendered="#{game.number gt game.guess and game.guess ne 0}" />
         <h:outputText id="Lower" value="Lower!"
                       rendered="#{game.number lt game.guess and game.guess ne 0}" />
      </div>

      <!-- Instructions for the user -->
      <div>
         I'm thinking of a number between <span
              id="numberGuess:smallest">#{game.smallest}</span> and <span
              id="numberGuess:biggest">#{game.biggest}</span>. You have
         #{game.remainingGuesses} guesses remaining.<!--(2)-->
      </div>

      <!-- Input box for the users guess, plus a button to submit, and reset -->
      <!-- These are bound using EL to our CDI beans -->
      <div>
         Your guess:
         <h:inputText id="inputGuess" value="#{game.guess}"
                      required="true" size="3"
                      disabled="#{game.number eq game.guess}"
                      validator="#{game.validateNumberRange}" /><!--(3)--><!--(4)-->
         <h:commandButton id="guessButton" value="Guess"
                          action="#{game.check}"
                          disabled="#{game.number eq game.guess}" /><!--(5)-->
      </div>
      <div>
         <h:commandButton id="restartButton" value="Reset"
                          action="#{game.reset}" immediate="true" />
      </div>
   </h:form>

</div>

<br style="clear: both" />

</body>
</html>
  1. There are a number of messages which can be sent to the user, "Higher!", "Lower!" and "Correct!"

  2. As the user guesses, the range of numbers they can guess gets smaller - this sentence changes to make sure they know the number range of a valid guess.

  3. This input field is bound to a bean property using a value expression.

  4. A validator binding is used to make sure the user doesn’t accidentally input a number outside of the range in which they can guess - if the validator wasn’t here, the user might use up a guess on an out of bounds number.

  5. And, of course, there must be a way for the user to send their guess to the server. Here we bind to an action method on the bean.

The example consists of 4 classes, two of which are qualifiers. First, there is the @Random qualifier, used for injecting a random number:

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

There is also the @MaxNumber qualifier, used for injecting the maximum number that can be injected:

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

The application-scoped Generator class is responsible for creating the random number, via a producer method. It also exposes the maximum possible number via a producer method:

@ApplicationScoped
public class Generator implements Serializable {

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

    private static final int MAX_NUMBER = 100;

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

    @Produces
    @Random
    int next() {
        //a number between 1 and 100
        return getRandom().nextInt(MAX_NUMBER - 1) + 1;
    }

    @Produces
    @MaxNumber
    int getMaxNumber() {
        return MAX_NUMBER;
    }
}

The Generator is application scoped, so we don’t get a different random each time.

Note
The package declaration and imports have been excluded from these listings. The complete listing is available in the example source code.

The final bean in the application is the session-scoped Game class. This is the primary entry point of the application. It’s responsible for setting up or resetting the game, capturing and validating the user’s guess and providing feedback to the user with a FacesMessage. We’ve used the post-construct lifecycle method to initialize the game by retrieving a random number from the @Random Instance<Integer> bean.

You’ll notice that we’ve also added the @Named annotation to this class. This annotation is only required when you want to make the bean accessible to a JSF view via EL (i.e., #{game}).

import jakarta.enterprise.inject.Instance;

@Named
@SessionScoped
public class Game implements Serializable {

    private static final int DEFAULT_REMAINING_GUESSES = 10;

    private int number;
    private int guess;
    private int smallest;
    private int biggest;
    private int remainingGuesses;

    @Inject
    @MaxNumber
    private int maxNumber;

    @Inject
    @Random
    private Instance<Integer> randomNumber;

    public Game() {
    }

    public int getNumber() {
        return number;
    }

    public int getGuess() {
        return guess;
    }

    public void setGuess(int guess) {
        this.guess = guess;
    }

    public int getSmallest() {
        return smallest;
    }

    public int getBiggest() {
        return biggest;
    }

    public int getRemainingGuesses() {
        return remainingGuesses;
    }

    public void check() {
        if (guess > number) {
            biggest = guess - 1;
        } else if (guess < number) {
            smallest = guess + 1;
        } else if (guess == number) {
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Correct!"));
        }
        remainingGuesses--;
    }

    @PostConstruct
    public void reset() {
        this.smallest = 0;
        this.guess = 0;
        this.remainingGuesses = DEFAULT_REMAINING_GUESSES;
        this.biggest = maxNumber;
        this.number = randomNumber.get();
    }

    public void validateNumberRange(FacesContext context, UIComponent toValidate, Object value) {
        if (remainingGuesses <= 0) {
            FacesMessage message = new FacesMessage("No guesses left!");
            context.addMessage(toValidate.getClientId(context), message);
            ((UIInput) toValidate).setValid(false);
            return;
        }
        int input = (Integer) value;

        if (input < smallest || input > biggest) {
            ((UIInput) toValidate).setValid(false);

            FacesMessage message = new FacesMessage("Invalid guess");
            context.addMessage(toValidate.getClientId(context), message);
        }
    }

    public boolean isGuessHigher() {
        return guess != 0 && guess > number;
    }

    public boolean isGuessLower() {
        return guess != 0 && guess < number;
    }

    public boolean isGuessCorrect() {
        return guess == number;
    }
}

The numberguess example in Apache Tomcat or Jetty

A couple of modifications must be made to the numberguess artifact in order to deploy it to Tomcat or Jetty. First, Weld must be deployed as a Web Application library under WEB-INF/lib since the servlet container does not provide the CDI services. For your convenience we provide a single jar suitable for running Weld in any servlet container (including Jetty), weld-servlet-shaded.

Note
You must also include the jars for JSF, EL, and the common annotations, all of which are provided by the Java EE platform (a Jakarta EE application server).

Second, we need to explicitly specify the servlet listener in web.xml, again because the container isn’t doing this stuff for you. The servlet listener boots Weld and controls its interaction with requests.

<listener>
   <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>

When Weld boots, it places the jakarta.enterprise.inject.spi.BeanManager, the portable SPI for obtaining bean instances, in the ServletContext under a variable name equal to the fully-qualified interface name. You generally don’t need to access this interface, but Weld makes use of it.

The numberguess example for Java SE with Swing

This example shows how to use the Weld SE extension in a Java SE based Swing application with no EJB or servlet dependencies. This example can be found in the examples/se/numberguess folder of the Weld distribution.

Running the example from the command line

  • Ensure that Maven 3 is installed and in your PATH

  • Ensure that the JAVA_HOME environment variable is pointing to your JDK installation

  • Open a command line or terminal window in the examples/se/numberguess directory

  • Execute the following command

    mvn -Drun

Understanding the code

Let’s have a look at the significant code and configuration files that make up this example.

There is an empty beans.xml file in the root package (src/main/resources/META-INF/beans.xml), which marks this application as a CDI application.

Note
The beans.xml file is no longer required for CDI enablement as of CDI 1.1. CDI is automatically enabled for archives which don’t contain beans.xml but contain one or more bean classes with a bean defining annotation, as described in section Implicit bean archive .

The game’s main logic is located in Game.java. Here is the code for that class, highlighting the ways in which this differs from the web application version:

@ApplicationScoped // (1)
public class Game { // (2)
    public static final int MAX_NUM_GUESSES = 10;

    private Integer number;
    private int guess = 0;
    private int smallest = 0;

    @Inject
    @MaxNumber
    private int maxNumber;

    private int biggest;
    private int remainingGuesses = MAX_NUM_GUESSES;
    private boolean validNumberRange = true;

    @Inject
    Generator rndGenerator;

    public Game() {
    }

    public int getNumber() {
        return number;
    }

    public int getGuess() {
        return guess;
    }

    public void setGuess(int guess) {
        this.guess = guess;
    }

    public int getSmallest() {
        return smallest;
    }

    public int getBiggest() {
        return biggest;
    }

    public int getRemainingGuesses() {
        return remainingGuesses;
    }

    public boolean isValidNumberRange() { // (3)
        return validNumberRange;
    }

    public boolean isGameWon() {
        return guess == number;
    }

    public boolean isGameLost() {
        return guess != number && remainingGuesses <= 0;
    }

    public boolean check() { // (4)
        boolean result = false;

        if (checkNewNumberRangeIsValid()) {
            if (guess > number) {
                biggest = guess - 1;
            }

            if (guess < number) {
                smallest = guess + 1;
            }

            if (guess == number) {
                result = true;
            }

            remainingGuesses--;
        }

        return result;
    }

    private boolean checkNewNumberRangeIsValid() {
        return validNumberRange = ((guess >= smallest) && (guess <= biggest));
    }

    @PostConstruct
    public void reset() { (5)
        this.smallest = 0;
        this.guess = 0;
        this.remainingGuesses = 10;
        this.biggest = maxNumber;
        this.number = rndGenerator.next();
        System.out.println("psst! the number is " + this.number);
    }
}
  1. The bean is application scoped rather than session scoped, since an instance of a Swing application typically represents a single 'session'.

  2. Notice that the bean is not named, since it doesn’t need to be accessed via EL.

  3. In Java SE there is no JSF FacesContext to which messages can be added. Instead the Game class provides additional information about the state of the current game including:

    • If the game has been won or lost

    • If the most recent guess was invalid

      This allows the Swing UI to query the state of the game, which it does indirectly via a class called MessageGenerator, in order to determine the appropriate messages to display to the user during the game.

  4. Since there is no dedicated validation phase, validation of user input is performed during the check() method.

  5. The reset() method makes a call to the injected rndGenerator in order to get the random number at the start of each game. Note that it can’t use Instance.get() like the JSF example does because there will not be any active contexts like there are during a JSF request.

The MessageGenerator class depends on the current instance of Game and queries its state in order to determine the appropriate messages to provide as the prompt for the user’s next guess and the response to the previous guess. The code for MessageGenerator is as follows:

public class MessageGenerator {
    @Inject // (1)
    private Game game;

    public String getChallengeMessage() { // (2)
        StringBuilder challengeMsg = new StringBuilder("I'm thinking of a number between ");
        challengeMsg.append(game.getSmallest());
        challengeMsg.append(" and ");
        challengeMsg.append(game.getBiggest());
        challengeMsg.append(". Can you guess what it is?");

        return challengeMsg.toString();
    }

    public String getResultMessage() { // (3)
        if (game.isGameWon()) {
            return "You guessed it! The number was " + game.getNumber();
        } else if (game.isGameLost()) {
            return "You are fail! The number was " + game.getNumber();
        } else if (!game.isValidNumberRange()) {
            return "Invalid number range!";
        } else if (game.getRemainingGuesses() == Game.MAX_NUM_GUESSES) {
            return "What is your first guess?";
        } else {
            String direction = null;

            if (game.getGuess() < game.getNumber()) {
                direction = "Higher";
            } else {
                direction = "Lower";
            }

            return direction + "! You have " + game.getRemainingGuesses() + " guesses left.";
        }
    }
}
  1. The instance of Game for the application is injected here.

  2. The `Game’s state is interrogated to determine the appropriate challenge message …​

  3. …​ and again to determine whether to congratulate, console or encourage the user to continue.

Finally we come to the NumberGuessFrame class which provides the Swing front end to our guessing game.

import jakarta.enterprise.event.Observes;

public class NumberGuessFrame extends javax.swing.JFrame {
    @Inject
    private Game game; // (1)

    @Inject
    private MessageGenerator msgGenerator; // (2)

    public void start(@Observes ContainerInitialized event) { // (3)
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                initComponents();
                setVisible(true);
            }
        });
    }

    private void initComponents() { // (4)

        borderPanel = new javax.swing.JPanel();
        gamePanel = new javax.swing.JPanel();
        inputsPanel = new javax.swing.JPanel();
        buttonPanel = new javax.swing.JPanel();
        guessButton = new javax.swing.JButton();
        ...
        mainLabel.setText(msgGenerator.getChallengeMessage());
        mainMsgPanel.add(mainLabel);

        messageLabel.setText(msgGenerator.getResultMessage());
        mainMsgPanel.add(messageLabel);
        ...
    }

    private void guessButtonActionPerformed(java.awt.event.ActionEvent evt) { // (5)
        int guess = -1;

        try {
            guess = Integer.parseInt(guessText.getText());
        } catch (NumberFormatException nfe) {
            // noop
        }

        game.setGuess(guess);
        game.check();
        refreshUI();

        if (game.isGameWon() || game.isGameLost()) {
            switchButtons();
        }
    }

    private void replayBtnActionPerformed(java.awt.event.ActionEvent evt) { // (6)
        game.reset();
        refreshUI();
        switchButtons();
    }

    private void switchButtons() {
        CardLayout buttonLyt = (CardLayout) buttonPanel.getLayout();
        buttonLyt.next(buttonPanel);
    }

    private void refreshUI() {
        mainLabel.setText(msgGenerator.getChallengeMessage());
        messageLabel.setText(msgGenerator.getResultMessage());
        guessText.setText("");
        guessesLeftBar.setValue(game.getRemainingGuesses());
        guessText.requestFocus();
    }

    // swing components
    private javax.swing.JPanel borderPanel;
    ...
    private javax.swing.JButton replayBtn;

}
  1. The injected instance of the game (logic and state).

  2. The injected message generator for UI messages.

  3. This application is started in the prescribed Weld SE way, by observing the ContainerInitialized event.

  4. This method initializes all the Swing components. Note the use of the msgGenerator here.

  5. guessButtonActionPerformed is called when the 'Guess' button is clicked, and it does the following:

    • Gets the guess entered by the user and sets it as the current guess in the Game

    • Calls game.check() to validate and perform one 'turn' of the game

    • Calls refreshUI. If there were validation errors with the input, this will have been captured during game.check() and as such will be reflected in the messages returned by MessageGenerator and subsequently presented to the user. If there are no validation errors then the user will be told to guess again (higher or lower) or that the game has ended either in a win (correct guess) or a loss (ran out of guesses).

    • Sets the button’s label based on the game state.

  6. replayBtnActionPerformed simply calls game.reset() to start a new game, refreshes the messages in the UI and sets the button’s label based on the game state.

That concludes our short tour of the Weld starter examples. For more information on Weld, please visit http://weld.cdi-spec.org/.

If you want to browse more Jakarta EE examples which leverage CDI technologies, there is a fair amount of them among WildFly Quickstarts.