PrimeFaces with Jakarta EE 10 made simple

In this article we will discuss how to create and deploy a Jakarta EE 10 application that uses Primefaces as Jakarta Faces implementation. We will build a sample application that will run in microservice-style as WildFly Bootable JAR.

PrimeFaces extends Jakarta Faces by providing a wide range of rich and customizable UI components that are not available in the standard Jakarta Faces implementation. Some of the top picks of PrimeFaces are:

  1. Rich Set of UI Components: PrimeFaces offers a comprehensive set of UI components that you can easily integrate into your JSF applications. These components include data tables, charts, calendars, dialog boxes, file uploaders, and more.
  2. Themes and Skinning: PrimeFaces provides a theming mechanism that allows you to easily change the look and feel of your application.
  3. Ajax Support: PrimeFaces heavily leverages Asynchronous JavaScript and XML (Ajax) to provide smooth and responsive user experiences. It offers built-in Ajax functionality for most components, reducing the need for manual JavaScript coding.

If you are new to Primefaces we recommend checking this introduction article: HelloWorld Primefaces tutorial

On the other hand, if you have got the basics of it, let’s see how to build a modern Jakarta EE 10 application which uses Primefaces.

Primefaces Jakarta EE 10 dependency

Firstly, in order to deploy our Primefaces example in a Jakarta EE 9/10 container you need the following set of dependencies:

<dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-api</artifactId>
        <version>10.0.0</version>
        <scope>provided</scope>
</dependency>
<dependency>
        <groupId>org.primefaces</groupId>
        <artifactId>primefaces</artifactId>
        <version>${primefaces.version}</version>
         <classifier>jakarta</classifier>
</dependency>

With regards to the Primefaces version, we will be using version 12.0.0:

<primefaces.version>12.0.0</primefaces.version>

Please notice the <classifier> element which you need to set to jakarta. The <classifier> is an optional element that you can use to specify a specific variant of an artifact when multiple versions of the same artifact are available with different characteristics. In our case, we need the version of Primefaces which uses jakarta packages instead of javax.

On the other hand, if you are are using Gradle, then you can specify the jakarta classifier as follows:

dependencies {
    implementation("org.primefaces:primefaces-12.0.0-jakarta")
}

Coding the Jakarta EE application

Our sample application will be a Primefaces datatable that shows a set of Data from the Database in tabular format. The original tutorial for this demo is in this article: PrimeFaces Datatable in a nutshell

The first change we will need to apply is moving the javax packages to jakarta. For example, here is the ItemView Class which delivers a View from a List of Item objects:

package com.sample.bean;
import java.io.Serializable;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.primefaces.event.RowEditEvent;

import com.sample.model.Item;
import com.sample.service.ItemService;

@Named("dtBasicView")
@ViewScoped
public class ItemView implements Serializable {

    private List<Item> items;

    @Inject
    private ItemService service;

    @PostConstruct
    public void init() {
        
        items = service.getItems();
    }

    public List<Item> getItems() {
        return items;
    }

    public void setService(ItemService service) {
        this.service = service;
    } 
    
    public void onRowEdit(RowEditEvent<Item> event) {
        FacesMessage msg = new FacesMessage("Product Edited", String.valueOf(event.getObject().getName()));
        FacesContext.getCurrentInstance().addMessage(null, msg);
        service.update(event.getObject());
    }

    public void onRowCancel(RowEditEvent<Item> event) {
        FacesMessage msg = new FacesMessage("Edit Cancelled", String.valueOf(event.getObject().getName()));
        FacesContext.getCurrentInstance().addMessage(null, msg);
    }

    public void onAddNew() {

        Item item = service.generateRandomItem();  
        items.add(item);
        System.out.println(item);
        service.save(item);

        FacesMessage msg = new FacesMessage("Item Product added", String.valueOf(item.getId()));
        FacesContext.getCurrentInstance().addMessage(null, msg);
    }
}

Next, if you are using an older Primefaces application, you should be aware that the @ManagedBean annotation as it is not available anymore in the context of a Jakarta EE application. You need to use CDI annotations such as @Named to reference your Beans.

Then, define your Service Class to access your Database. We will also be using JavaFaker library to generate new random Item objects:

@Named
@ApplicationScoped
public class ItemService {
	List<Item> items;

	@PersistenceContext
	private EntityManager em;

	@PostConstruct
	public void init() {
		Query query = em.createQuery("SELECT i FROM Item i");
		items = query.getResultList();
	}

	public List<Item> getItems() {
		return items;
	}

	public void setItems(List<Item> items) {
		this.items = items;
	}

	public Item generateRandomItem() {
		Faker faker = new Faker();

		Item item = new Item();
		item.setCategory(faker.commerce().department());
		item.setName(faker.commerce().productName());
		item.setPrice((Double.valueOf(faker.commerce().price(10, 1000))));
		return item;
	}


	@Transactional
	public void update(Item item) {
		Integer id = item.getId();
		Item itemdb = em.find(Item.class, id);
		itemdb.setCategory(item.getCategory());
		itemdb.setName(item.getName());
		itemdb.setPrice(item.getPrice());
		em.persist(itemdb);

	}

	@Transactional
	public void save(Item item) {		
		em.persist(item);
	}

}

For the sake of completeness, here is the Item Class which is our Entity object that maps an equivalent Database table:

@Entity
public class Item {

	@Id
	@GeneratedValue
	Integer id;
	String name;
	String category;
	Double price;

      // getters/setters omitted for brevity

}

Coding the View

Next, we will add a simple index.html which contains a dataTable component within a form and a button to add some random Item objects:

    <h:form id="form">
        <p:growl id="msgs" showDetail="true"/>

        <p:dataTable id="items" widgetVar="items" var="item" value="#{dtBasicView.items}"
                     editable="true">
            <f:facet name="header">
                Jakarta EE 10 demo with Primefaces
            </f:facet>

            <p:ajax event="rowEdit" listener="#{dtBasicView.onRowEdit}" update=":form:msgs"/>
            <p:ajax event="rowEditCancel" listener="#{dtBasicView.onRowCancel}" update=":form:msgs"/>

            <p:column headerText="Name">
                <p:cellEditor>
                    <f:facet name="output"><h:outputText value="#{item.name}"/></f:facet>
                    <f:facet name="input"><p:inputText value="#{item.name}" style="width:100%"
                                                       label="Name"/></f:facet>
                </p:cellEditor>
            </p:column>

            <p:column headerText="Code">
                <p:cellEditor>
                    <f:facet name="output"><h:outputText value="#{item.category}"/></f:facet>
                    <f:facet name="input"><p:inputText id="modelInput" value="#{item.category}"
                                                       style="width:100%"/></f:facet>
                </p:cellEditor>
            </p:column>

            <p:column headerText="Price">
                <p:cellEditor>
                    <f:facet name="output"><h:outputText value="#{item.price}"/></f:facet>
                    <f:facet name="input"><p:inputText value="#{item.price}" style="width:100%"
                                                       label="Price"/></f:facet>
                </p:cellEditor>
            </p:column>


            <p:column style="width:6rem">
                <p:rowEditor/>
            </p:column>
        </p:dataTable>

        <div class="grid mt-3">
            <div class="col-12">
                <p:commandButton value="Add new row" process="@this" update=":form:msgs"
                                 action="#{dtBasicView.onAddNew()}" oncomplete="PF('items').addRow();"/>
            </div>
        </div>

    </h:form>

Note how binding of events work in order to update the datatable dynamically:

  • When you Edit a Row of the Table, the dtBasicView.onRowEdit method will be triggered.
  • When you Cancel Editing a Row of the Table the dtBasicView.onRowCancel method will be triggered.
  • Finally, when you Add a Row, the listener is dtBasicView.onAddNew. After, when this listener completes, the oncomplete method is invoked to update the dataTable,

Building the Bootable JAR

Next, to build a Bootable JAR file, we need to add the set of layers our Web application needs in the wildfly-jar-maven-plugin. For our example, we will need the the jsf, jpa and h2-default-datasource layers:

 <plugin>
        <groupId>org.wildfly.plugins</groupId>
        <artifactId>wildfly-jar-maven-plugin</artifactId>
        <version>${version.bootable-jar}</version>
        <configuration>
          <feature-pack-location>wildfly@maven(org.jboss.universe:community-universe)#${version.wildfly}</feature-pack-location>
          <layers>
            <layer>jsf</layer>
            <layer>jpa</layer>
            <layer>h2-default-datasource</layer>
          </layers>
          <plugin-options>
            <jboss-fork-embedded>${plugin.fork.embedded}</jboss-fork-embedded>
          </plugin-options>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>package</goal>
            </goals>
          </execution>
        </executions>
</plugin>

Then, we can build the application with:

mvn clean install wildfly-jar:run

WildFly will start, registering our application under the root Web context:

19:47:59,923 INFO  [org.primefaces.webapp.PostConstructApplicationEventListener] (ServerService Thread Pool -- 32) Running on PrimeFaces 12.0.0
19:47:59,931 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 32) WFLYUT0021: Registered web context: '/' for server 'default-server'
19:47:59,934 INFO  [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0010: Deployed "jakartaee-demo.war" (runtime-name : "ROOT.war")
19:47:59,964 INFO  [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0212: Resuming server
19:47:59,967 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 28.0.0.Final (WildFly Core 20.0.1.Final) started in 4948ms - Started 332 of 404 services (131 services are lazy, passive or on-demand) - Server configuration file in use: standalone.xml

You can reach it under the default address http://localhost:8080

primefaces jakarta ee tutorial jsf

Our application contains some default Item objects in the import.sql script. You can add new Items in the dataTable by clicking on the “Add Random” button or you can Edit the existing ones by clicking on the last column icon.

Conclusion

In this article we discussed how to integrate PrimeFaces with a Jakarta EE 10 application. Overall, PrimeFaces simplifies and enhances the development of JSF-based applications by providing a feature-rich set of UI components, Ajax support, theming, and mobile compatibility. Its active community and ongoing development make it a reliable choice for building modern and responsive web applications with JSF.

Source code for this article: https://github.com/fmarchioni/mastertheboss/tree/master/web/primefaces/prime-jakartaee

Found the article helpful? if so please follow us on Socials