Building a dynamic web application with integrated user interface and backend logic

duration 25 minutes
New

Prerequisites:

Learn how to build a dynamic web application using Jakarta Faces, Jakarta Contexts and Dependency Injection, and Jakarta Expression Language.

What you’ll learn

You’ll learn how to build a dynamic web application using Jakarta Faces for the user interface (UI), Jakarta Contexts and Dependency Injection (CDI) for managing backend logic, and Jakarta Expression Language (EL) for data binding.

Jakarta Faces is a framework for building component-based web applications that simplifies UI development by managing reusable components, handling user interactions, and binding data to backend logic. It provides built-in lifecycle management, event handling, and server-side validation, reducing the need for manual request processing. Jakarta Faces also includes tag libraries that allows developers define UI components using markup and connect them to backend objects without writing repetitive setup code.

To further streamline development, Jakarta Faces works with CDI to manage backend components. CDI allows beans to be automatically created and injected where needed, making it easier to manage application logic. Jakarta Expression Language enables data binding between the UI and backend, allowing UI components to dynamically display data and trigger backend actions.

The application you will build in this guide is a dynamic web application that displays system load data on demand. Using Jakarta Faces for the UI, you’ll create a table to show the system CPU load and heap memory usage. You’ll also learn how to use CDI to provide the system load data from a managed bean, and to use Jakarta Expression Language to bind this data to the UI components.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-jakarta-faces.git
cd guide-jakarta-faces

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run Maven with the liberty:run goal to build the application and deploy it to Open Liberty:

cd finish
mvn liberty:run

After you see the following message, your Liberty instance is ready.

The defaultServer server is ready to run a smarter planet.

Check out the web application at the http://localhost:9080/index.xhtml URL. Click the refresh icon refresh button, located next to the table title, to update and display the latest system load data in the table.

After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C in the command-line session where you ran Liberty. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Creating a static Jakarta Faces page

Start by creating a page that displays an empty table by using Jakarta Faces to extend standard HTML. The table will display the system load data and serves as the starting point for your application.

Navigate to the start directory to begin.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

Create the index.xhtml file.
src/main/webapp/index.xhtml

index.xhtml

 1<!-- tag::copyright[] -->
 2<!--
 3  Copyright (c) 2024 IBM Corp.
 4
 5  Licensed under the Apache License, Version 2.0 (the "License");
 6  you may not use this file except in compliance with the License.
 7  You may obtain a copy of the License at
 8
 9    http://www.apache.org/licenses/LICENSE-2.0
10
11  Unless required by applicable law or agreed to in writing, software
12  distributed under the License is distributed on an "AS IS" BASIS,
13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  See the License for the specific language governing permissions and
15  limitations under the License.
16-->
17<!-- end::copyright[] -->
18<!DOCTYPE html>
19<!-- tag::xmlns[] -->
20<html xmlns="http://www.w3.org/1999/xhtml"
21      xmlns:h="jakarta.faces.html"
22      xmlns:f="jakarta.faces.core"
23      xmlns:ui="jakarta.faces.facelets">
24<!-- end::xmlns[] -->
25
26  <h:head>
27    <meta charset="UTF-8" />
28    <title>Open Liberty - Jakarta Faces Example</title>
29    <!-- tag::outputStylesheet[] -->
30    <h:outputStylesheet library="css" name="styles.css" />
31    <!-- end::outputStylesheet[] -->
32    <link href="favicon.ico" rel="icon" />
33    <link href="favicon.ico" rel="shortcut icon" />
34  </h:head>
35  <h:body>
36    <section id="appIntro">
37      <div id="titleSection">
38        <h1 id="appTitle">Jakarta Faces Example</h1>
39        <div class="line"></div>
40        <div class="headerImage"></div>
41      </div>
42
43      <div class="msSection" id="systemLoads">
44        <div class="headerRow">
45          <div class="headerIcon">
46            <img src="#{resource['img/sysProps.svg']}" />
47          </div>
48          <div class="headerTitleWithButton" id="sysPropTitle">
49            <h2>System Loads</h2>
50          </div>
51        </div>
52        <div class="sectionContent">
53          <!-- tag::dataTable[] -->
54          <h:dataTable id="systemLoadsTable">
55            <h:column>
56              <f:facet name="header">Time</f:facet>
57            </h:column>
58            <h:column>
59              <f:facet name="header">CPU Load (%)</f:facet>
60            </h:column>
61            <h:column>
62              <f:facet name="header">Heap Memory Usage (%)</f:facet>
63            </h:column>
64          </h:dataTable>
65          <!-- end::dataTable[] -->
66        </div>
67      </div>
68    </section>
69    <!-- tag::uiIncludeFooter[] -->
70    <ui:include src="/WEB-INF/includes/footer.xhtml" />
71    <!-- end::uiIncludeFooter[] -->
72  </h:body>
73</html>

footer.xhtml

 1<!-- tag::copyright[] -->
 2<!--
 3  Copyright (c) 2024 IBM Corp.
 4
 5  Licensed under the Apache License, Version 2.0 (the "License");
 6  you may not use this file except in compliance with the License.
 7  You may obtain a copy of the License at
 8
 9    http://www.apache.org/licenses/LICENSE-2.0
10
11  Unless required by applicable law or agreed to in writing, software
12  distributed under the License is distributed on an "AS IS" BASIS,
13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  See the License for the specific language governing permissions and
15  limitations under the License.
16-->
17<!-- end::copyright[] -->
18<ui:composition xmlns="http://www.w3.org/1999/xhtml"
19                xmlns:h="jakarta.faces.html"
20                xmlns:ui="jakarta.faces.facelets">
21
22  <footer>
23    <div id="footer_gray_background">
24      <div id="footer_top_container" class="footer_container container-fluid">
25        <div id="footer_project_container">
26          <span id="footer_open_liberty"></span>
27          <p id="footer_project">
28            <h:outputText value="an IBM open source project" />
29          </p>
30        </div>
31        <div id="footer_round_links_container">
32          <a id="footer_github_link" class="footer_round_btn"
33             href="https://github.com/OpenLiberty/"
34             target="_blank" rel="noopener" aria-label="Github">
35          </a>
36          <a id="footer_stackoverflow_link" class="footer_round_btn"
37             href="https://stackoverflow.com/questions/tagged/open-liberty"
38             target="_blank" rel="noopener"
39             aria-label="Open Liberty questions on Stack Overflow">
40          </a>
41          <a id="footer_groupsio_link" class="footer_round_btn"
42             href="https://groups.io/g/openliberty" 
43             target="_blank" rel="noopener" aria-label="Open Liberty Groups.io">
44          </a>
45          <a id="footer_gitter_link" class="footer_round_btn"
46             href="https://app.gitter.im/#/room/#OpenLiberty_development:gitter.im"
47             target="_blank" rel="noopener" aria-label="Open Liberty Gitter">
48          </a>
49        </div>
50      </div>
51    </div>
52
53    <div id="footer_bottom_container" class="footer_container container-fluid">
54      <div id="footer_bottom_left_section">
55        <p id="footer_copyright">&#169; IBM Corp 2024</p>
56        <p class="vertical_bar">|</p>
57        <a id="footer_privacy_policy_link"
58           href="https://www.ibm.com/privacy/"
59           target="_blank" rel="noopener" class="left_footer_link">
60          <h:outputText value="Privacy policy" />
61        </a>
62        <p class="vertical_bar">|</p>
63        <a id="footer_license_link"
64           href="https://github.com/OpenLiberty/open-liberty/blob/release/LICENSE"
65           target="_blank" rel="noopener" class="left_footer_link">
66          <h:outputText value="License" />
67        </a>
68        <p class="vertical_bar">|</p>
69        <a id="footer_logos_link"
70           href="https://github.com/OpenLiberty/logos"
71           target="_blank" rel="noopener" class="left_footer_link">
72          <h:outputText value="Logos" />
73        </a>
74      </div>
75      <div id="footer_bottom_right_section">
76        <a id="footer_docs_link"
77           href="/docs" aria-label="Open Liberty Docs"
78           class="right_footer_link blue_link_light_background">
79          <h:outputText value="Docs" />
80        </a>
81        <a id="footer_blog_link"
82           href="/blog" aria-label="Open Liberty Blog"
83           class="right_footer_link blue_link_light_background">
84          <h:outputText value="Blog" />
85        </a>
86        <a id="footer_support_link"
87           href="/support" aria-label="Open Liberty Support"
88           class="right_footer_link blue_link_light_background">
89          <h:outputText value="Support" />
90        </a>
91      </div>
92    </div>
93  </footer>
94</ui:composition>

In the index.xhtml file, the xmlns attributes define the XML namespaces for various Jakarta Faces tag libraries. These namespaces allow the page to use Jakarta Faces tags for templating, creating UI components, and enabling core functionality, such as form submissions and data binding. For more information on the various tag libraries and their roles in Jakarta Faces, refer to the Jakarta Faces Tag Libraries and the VDL Documentation Generator documentation.

The index.xhtml file combines standard HTML elements with Jakarta Faces components, providing both static layout and dynamic functionality. Standard HTML elements, like div and section, structure the page’s layout. Jakarta Faces tags offer additional features beyond standard HTML, such as managing UI components, including resources, and binding data. For example, the h:outputStylesheet tag loads a CSS file for styling, and the ui:include tag incorporates reusable components, such as the provided footer.xhtml file, to streamline maintenance and reuse across multiple pages. The h:dataTable tag is used to display a table.

At this point, the page defines a table that has no data entries. We’ll add dynamic content in the following steps.

Configuring the Faces Servlet

Before you can access the Jakarta Faces page, you need to configure a Faces servlet in your application. This servlet handles all requests for .xhtml pages and processes them using Jakarta Faces.

Create the web.xml file.
src/main/webapp/WEB-INF/web.xml

web.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
 3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
 5         version="6.0">
 6
 7    <context-param>
 8        <!-- tag::project-stage[] -->
 9        <param-name>jakarta.faces.PROJECT_STAGE</param-name>
10        <!-- end::project-stage[] -->
11        <!-- tag::project-stage-dev[] -->
12        <param-value>Development</param-value>
13        <!-- end::project-stage-dev[] -->
14    </context-param>
15
16    <!-- Faces Servlet Configuration -->
17    <!-- tag::servlet[] -->
18    <servlet>
19        <servlet-name>Faces Servlet</servlet-name>
20        <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
21        <!-- tag::load-on-startup[] -->
22        <load-on-startup>1</load-on-startup>
23        <!-- end::load-on-startup[] -->
24    </servlet>
25    <!-- end::servlet[] -->
26
27    <!-- Servlet Mapping -->
28    <!-- tag::servlet-mapping[] -->
29    <servlet-mapping>
30        <servlet-name>Faces Servlet</servlet-name>
31        <url-pattern>*.xhtml</url-pattern>
32    </servlet-mapping>
33    <!-- end::servlet-mapping[] -->
34
35</web-app>

The servlet element defines the Faces servlet that is responsible for processing requests for Jakarta Faces pages. The load-on-startup element with a value of 1 specifies that the servlet is loaded and initialized first when the application starts.

The servlet-mapping element specifies which URL patterns are routed to the Faces servlet. In this case, all URLs ending with .xhtml are mapped to be processed by Jakarta Faces. This ensures that any request for an .xhtml page is handled by the Faces servlet, which manages the lifecycle of Jakarta Faces components, processes the page, and renders the output.

By configuring both the servlet and the servlet mapping, you’re ensuring that Jakarta Faces pages are properly processed and delivered in response to user requests.

The jakarta.faces.PROJECT_STAGE context parameter determines the current stage of the application in its development lifecycle. Because it is currently set to Development, you will see additional debugging information, including developer-friendly warning messages such as WARNING: Apache MyFaces Core is running in DEVELOPMENT mode. For more information about valid values and how to set the PROJECT_STAGE parameter, see the official Jakarta Faces ProjectStage documentation.

In your dev mode console, type r and press the enter/return key to restart the Liberty instance so that Liberty reads the configuration changes. When you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Check out the web application that you created at the http://localhost:9080/index.xhtml URL.

You should see the static page with the system loads table displaying only the headers and no data.

Implementing backend logic with dependency injection

To provide system load data to your web application, you’ll create a CDI-managed bean that retrieves information about the system CPU load and memory usage. This bean is accessible from the Jakarta Faces page and supplies the data that is displayed.

Create the SystemLoadBean class.
src/main/java/io/openliberty/guides/bean/SystemLoadBean.java

SystemLoadBean.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2024 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.bean;
13
14import java.lang.management.ManagementFactory;
15import java.lang.management.MemoryMXBean;
16import java.util.ArrayList;
17import java.util.Calendar;
18import java.util.List;
19import java.io.Serializable;
20
21import jakarta.annotation.PostConstruct;
22import jakarta.enterprise.context.ApplicationScoped;
23import jakarta.inject.Named;
24
25import com.sun.management.OperatingSystemMXBean;
26
27import io.openliberty.guides.bean.model.SystemLoadData;
28
29// tag::namedAnnotation[]
30@Named("systemLoadBean")
31// end::namedAnnotation[]
32// tag::applicationScopedAnnotation[]
33@ApplicationScoped
34// end::applicationScopedAnnotation[]
35// tag::systemLoadBean[]
36public class SystemLoadBean implements Serializable {
37    private static final long serialVersionUID = 1L;
38
39    private List<SystemLoadData> systemLoads;
40
41    private static final OperatingSystemMXBean OS =
42        (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
43
44    private static final MemoryMXBean MEM =
45        ManagementFactory.getMemoryMXBean();
46
47    // tag::postConstruct[]
48    @PostConstruct
49    // end::postConstruct[]
50    // tag::init[]
51    public void init() {
52        systemLoads = new ArrayList<>();
53        fetchSystemLoad();
54    }
55    // end::init[]
56
57    // tag::fetchSystemLoadMethod[]
58    public void fetchSystemLoad() {
59        String time = Calendar.getInstance().getTime().toString();
60
61        double cpuLoad = OS.getCpuLoad() * 100;
62
63        long heapMax = MEM.getHeapMemoryUsage().getMax();
64        long heapUsed = MEM.getHeapMemoryUsage().getUsed();
65        double memoryUsage = heapUsed * 100.0 / heapMax;
66
67        SystemLoadData data = new SystemLoadData(time, cpuLoad, memoryUsage);
68
69        systemLoads.add(data);
70    }
71    // end::fetchSystemLoadMethod[]
72
73    // tag::getSystemLoads[]
74    public List<SystemLoadData> getSystemLoads() {
75        return systemLoads;
76    }
77    // end::getSystemLoads[]
78}
79// end::systemLoadBean[]

Annotate the SystemLoadBean class with a @Named annotation to make it accessible in the Jakarta Faces pages under the systemLoadBean name. Because the SystemLoadBean bean is a CDI-managed bean, a scope is necessary. Annotating it with the @ApplicationScoped annotation indicates that it is initialized once and is shared between all requests while the application runs. To learn more about CDI, see the Injecting dependencies into microservices guide.

The @PostConstruct annotation ensures the init() method runs after the SystemLoadBean is initialized and dependencies are injected. The init() method sets up any required resources for the bean’s lifecyccle.

The fetchSystemLoad() method retrieves the current system load and memory usage, then updates the list of system load data.

The getSystemLoads() method is a getter method for accessing the list of system load data from the Jakarta Faces page.

Binding data to the UI with expression language

Now that you have implemented the backend logic with CDI, you’ll update the Jakarta Faces page to display the dynamic system load data. You’ll do this by using Jakarta Expression Language to bind the UI components to the backend data.

Replace the index.xhtml file.
src/main/webapp/index.xhtml

index.xhtml

  1<!-- tag::copyright[] -->
  2<!--
  3  Copyright (c) 2024 IBM Corp.
  4
  5  Licensed under the Apache License, Version 2.0 (the "License");
  6  you may not use this file except in compliance with the License.
  7  You may obtain a copy of the License at
  8
  9    http://www.apache.org/licenses/LICENSE-2.0
 10
 11  Unless required by applicable law or agreed to in writing, software
 12  distributed under the License is distributed on an "AS IS" BASIS,
 13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  See the License for the specific language governing permissions and
 15  limitations under the License.
 16-->
 17<!-- end::copyright[] -->
 18<!DOCTYPE html>
 19<!-- tag::xmlns[] -->
 20<html xmlns="http://www.w3.org/1999/xhtml"
 21      xmlns:h="jakarta.faces.html"
 22      xmlns:f="jakarta.faces.core"
 23      xmlns:ui="jakarta.faces.facelets">
 24<!-- end::xmlns[] -->
 25  <h:head>
 26    <meta charset="UTF-8" />
 27    <title>Open Liberty - Jakarta Faces Example</title>
 28    <h:outputStylesheet library="css" name="styles.css" />
 29    <link href="favicon.ico" rel="icon" />
 30    <link href="favicon.ico" rel="shortcut icon" />
 31  </h:head>
 32  <h:body>
 33    <section id="appIntro">
 34      <div id="titleSection">
 35        <h1 id="appTitle">Jakarta Faces Example</h1>
 36        <div class="line"></div>
 37        <div class="headerImage"></div>
 38      </div>
 39
 40      <div class="msSection" id="systemLoads">
 41        <!-- tag::systemLoadForm[] -->
 42        <h:form id="systemLoadForm">
 43          <div class="headerRow">
 44            <div class="headerIcon">
 45              <img src="#{resource['img/sysProps.svg']}" />
 46            </div>
 47            <div class="headerTitleWithButton" id="sysPropTitle">
 48              <h2>System Loads</h2>
 49              <!-- tag::commandButton[] -->
 50              <!-- tag::commandButtonAction[] -->
 51              <h:commandButton id="refreshButton" styleClass="refreshButton" value=""
 52                               title="Refresh system load data"
 53                               action="#{systemLoadBean.fetchSystemLoad}" >
 54              <!-- end::commandButtonAction[] -->
 55                <!-- tag::ajaxTag[] -->
 56                <f:ajax render="systemLoadForm" />
 57                <!-- end::ajaxTag[] -->
 58              </h:commandButton>
 59              <!-- end::commandButton[] -->
 60            </div>
 61          </div>
 62          <div class="sectionContent">
 63            <!-- tag::systemLoadsTable[] -->
 64            <!-- tag::dataBind[] -->
 65            <h:dataTable id="systemLoadsTable"
 66                         value="#{systemLoadBean.systemLoads}"
 67                         var="systemLoadData"
 68                         styleClass = "systemLoadsTable"
 69                         headerClass = "systemLoadsTableHeader"
 70                         rowClasses = "systemLoadsTableOddRow,systemLoadsTableEvenRow">
 71            <!-- end::dataBind[] -->
 72              <h:column>
 73                <f:facet name="header">Time</f:facet>
 74                <!-- tag::outputText1[] -->
 75                <h:outputText value="#{systemLoadData.time}" />
 76                <!-- end::outputText1[] -->
 77              </h:column>
 78  
 79              <h:column>
 80                <f:facet name="header">CPU Load (%)</f:facet>
 81                <!-- tag::outputText2[] -->
 82                <h:outputText
 83                  value="#{systemLoadData.cpuLoad == null ? '-' : systemLoadData.cpuLoad}">
 84                  <!-- tag::convertNumber1[] -->
 85                  <f:convertNumber pattern="#0.0000000" />
 86                  <!-- end::convertNumber1[] -->
 87                </h:outputText>
 88                <!-- end::outputText2[] -->
 89              </h:column>
 90  
 91              <h:column>
 92                <f:facet name="header">Heap Memory Usage (%)</f:facet>
 93                <!-- tag::outputText3[] -->
 94                <h:outputText
 95                  value="#{systemLoadData.memoryUsage == null ? '-' : systemLoadData.memoryUsage}">
 96                  <!-- tag::convertNumber2[] -->
 97                  <f:convertNumber pattern="#0.00" />
 98                  <!-- end::convertNumber2[] -->
 99                </h:outputText>
100                <!-- end::outputText3[] -->
101              </h:column>
102            </h:dataTable>
103            <!-- end::systemLoadsTable[] -->
104          </div>
105        </h:form>
106        <!-- end::systemLoadForm[] -->
107      </div>
108    </section>
109    <ui:include src="/WEB-INF/includes/footer.xhtml" />
110  </h:body>
111</html>

SystemLoadBean.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2024 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.bean;
13
14import java.lang.management.ManagementFactory;
15import java.lang.management.MemoryMXBean;
16import java.util.ArrayList;
17import java.util.Calendar;
18import java.util.List;
19import java.io.Serializable;
20
21import jakarta.annotation.PostConstruct;
22import jakarta.enterprise.context.ApplicationScoped;
23import jakarta.inject.Named;
24
25import com.sun.management.OperatingSystemMXBean;
26
27import io.openliberty.guides.bean.model.SystemLoadData;
28
29// tag::namedAnnotation[]
30@Named("systemLoadBean")
31// end::namedAnnotation[]
32// tag::applicationScopedAnnotation[]
33@ApplicationScoped
34// end::applicationScopedAnnotation[]
35// tag::systemLoadBean[]
36public class SystemLoadBean implements Serializable {
37    private static final long serialVersionUID = 1L;
38
39    private List<SystemLoadData> systemLoads;
40
41    private static final OperatingSystemMXBean OS =
42        (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
43
44    private static final MemoryMXBean MEM =
45        ManagementFactory.getMemoryMXBean();
46
47    // tag::postConstruct[]
48    @PostConstruct
49    // end::postConstruct[]
50    // tag::init[]
51    public void init() {
52        systemLoads = new ArrayList<>();
53        fetchSystemLoad();
54    }
55    // end::init[]
56
57    // tag::fetchSystemLoadMethod[]
58    public void fetchSystemLoad() {
59        String time = Calendar.getInstance().getTime().toString();
60
61        double cpuLoad = OS.getCpuLoad() * 100;
62
63        long heapMax = MEM.getHeapMemoryUsage().getMax();
64        long heapUsed = MEM.getHeapMemoryUsage().getUsed();
65        double memoryUsage = heapUsed * 100.0 / heapMax;
66
67        SystemLoadData data = new SystemLoadData(time, cpuLoad, memoryUsage);
68
69        systemLoads.add(data);
70    }
71    // end::fetchSystemLoadMethod[]
72
73    // tag::getSystemLoads[]
74    public List<SystemLoadData> getSystemLoads() {
75        return systemLoads;
76    }
77    // end::getSystemLoads[]
78}
79// end::systemLoadBean[]

styles.css

  1/* tag::copyright[] */
  2/*******************************************************************************
  3* Copyright (c) 2024 IBM Corporation and others.
  4* All rights reserved. This program and the accompanying materials
  5* are made available under the terms of the Eclipse Public License v1.0
  6* which accompanies this distribution, and is available at
  7* http://www.eclipse.org/legal/epl-v10.html
  8*
  9* Contributors:
 10*     IBM Corporation - initial API and implementation
 11*******************************************************************************/
 12/* end::copyright[] */
 13@font-face {
 14    font-family: BunueloLight;
 15    src: url('#{resource["fonts/BunueloCleanPro-Light.otf"]}');
 16}
 17
 18@font-face {
 19    font-family: BunueloSemiBold;
 20    src: url('#{resource["fonts/BunueloCleanPro-SemiBold.otf"]}');
 21}
 22
 23body {
 24    font-family: BunueloSemiBold, sans-serif;
 25    font-size: 16px;
 26    color: #24243b;
 27    background-color: white;
 28    margin: 0px;
 29}
 30
 31section {
 32    padding-top: 55px;
 33    padding-left: 8%;
 34    padding-right: 8%;
 35    letter-spacing: 0;
 36    text-align: left;
 37}
 38
 39.line {
 40    margin-right: 200px;
 41    height: 1px;
 42    background-color: #C8D3D3;
 43}
 44
 45.headerImage {
 46    background-image: url('#{resource["img/header_ufo.png"]}');
 47    background-repeat: no-repeat;
 48    background-position: top 20px right 15px;
 49    height: 103px;
 50    margin-top: -94px;
 51}
 52
 53#whereTo {
 54    padding-bottom: 80px;
 55    width: 50%;
 56}
 57
 58p {
 59    line-height: 22px;
 60    margin-top: 0px;
 61}
 62
 63h1 {
 64    font-family: BunueloSemiBold;
 65    font-size: 40px;
 66    font-weight: 400;
 67    letter-spacing: 0;
 68    text-align: left;
 69}
 70
 71h2 {
 72    font-size: 24px;
 73    font-weight: 400;
 74}
 75
 76h4 {
 77    margin-top: 52px;
 78}
 79
 80a {
 81    text-decoration: none;
 82}
 83
 84#appIntro {
 85    background-image: linear-gradient(#141427 0%, #2c2e50 100%);
 86    background-size: 100% calc(100% - 70px);
 87    background-repeat: no-repeat;
 88}
 89
 90#titleSection {
 91    color: white;
 92    margin-bottom: 80px;
 93}
 94
 95#appTitle {
 96    font-family: BunueloLight;
 97    font-size: 55px;
 98}
 99
100.headerRow {
101    height: 100px;
102    position: relative;
103    z-index: 2;
104    box-shadow: 0 2px 4px 0 rgba(0,0,0,0.50);
105}
106
107.headerRow > div {
108    display: flex;
109}
110
111.headerTitleWithButton {
112    display: flex;
113    align-items: center;
114    gap: 10px;
115    background-color: white;
116    color: #5d6a8e;
117    letter-spacing: 0;
118    text-align: left;
119    padding-left: 40px;
120    padding-top: 10px;
121    width: calc(100% - 200px);
122}
123
124.headerTitleWithButton > h2 {
125    font-family: BunueloLight;
126    font-size: 40px;
127    margin: 0;
128}
129
130.refreshButton {
131    background-image: url('#{resource["img/refresh.svg"]}');
132    background-repeat: no-repeat;
133    background-position: center;
134    background-color: transparent;
135    border: none;
136    width: 60px;
137    height: 60px;
138    cursor: pointer;
139    padding: 0;
140    margin: 0;
141    border-radius: 50%;
142    transition: background-color 0.2s, width 0.2s, height 0.2s, border-radius 0.2s;
143}
144
145.refreshButton:hover {
146    background-color: #dddddd;
147}
148
149.collapsibleRow {
150    transition: border 400ms ease-out, box-shadow 200ms linear;
151    cursor: pointer;
152}
153
154.collapsibleRow:hover .headerTitle {
155    background-color: #f4f4f4;
156    transition: background-color 0.1s;
157}
158
159.collapsed .collapsibleRow {
160    box-shadow: none;
161    border-bottom: 4px solid;
162}
163
164.collapsed#healthSection > .headerRow {
165    border-bottom-color: #D6D9E4;
166}
167
168.collapsed#configSection > .headerRow {
169    border-bottom-color: #F8D7C1;
170}
171
172.collapsed#metricsSection > .headerRow {
173    border-bottom-color: #EEF3C3;
174}
175
176.collapsed .collapsibleContent {
177    transition: all 400ms ease-out, opacity 300ms ease-in;
178    opacity: 0;
179    max-height: 0;
180    visibility: hidden;
181}
182
183.expanded .collapsibleContent {
184    transition: all 400ms ease-out, opacity 450ms ease-out;
185    opacity: 1;
186    max-height: 1000px;
187    visibility: visible;
188}
189
190.headerIcon {
191    width: 160px;
192    height: 100%;
193    float: left;
194    background-color: #E8EAEF;
195}
196
197.headerIcon img {
198    display: block;
199    margin: auto;
200    margin-top: 20px;
201}
202
203#healthSection .headerIcon {
204    background-color: #E8EAEF;
205}
206
207#configSection .headerIcon {
208    background-color: #FDE4D1;
209}
210
211#metricsSection .headerIcon {
212    background-color: #F5F8DA;
213}
214
215#healthSection h2 {
216    color: #5D6A8E;
217}
218
219#configSection h2 {
220    color: #E57000;
221}
222
223#metricsSection h2 {
224    color: #4F6700;
225}
226
227#sysPropTitle {
228    padding-top: 28px;
229}
230
231.caret {
232    position: absolute;
233    right: 45px;
234    top: 45px;
235}
236
237.msSection {
238    background: white;
239    box-shadow: 0 2px 4px 0 rgba(63,70,89,0.31);
240}
241
242.sectionContent {
243    margin-left: 160px;
244}
245
246#systemLoadsTable {
247    padding-left: 160px;
248    background: white;
249}
250
251#guidesButton {
252    background-color: #abd155;
253    width: 269px;
254    font-weight: 500;
255    font-size: 16px;
256    transition: background-color .2s;
257}
258
259#guidesButton:hover {
260    background-color: #C7EE63;
261}
262
263#mpGuidesButton {
264    border: 2px solid #f4914d8c;
265    border-radius: 100px;
266    font-size: 20px;
267    letter-spacing: 0;
268    padding-left: 40px;
269    padding-right: 40px;
270    background-color: white;
271    transition: background-color .2s, color .2s;
272}
273
274#mpGuidesButton:hover {
275    background-color: #f4914d;
276    color: white;
277}
278
279section#openLibertyAndMp {
280    background: #f4f4f5;
281    background-size: 100% calc(100% - 70px);
282    background-repeat: no-repeat;
283}
284
285#healthBox {
286    text-align: left;
287    display: table-cell;
288    vertical-align: middle;
289    width: 47%;
290}
291
292#healthBox > div {
293    display: table-cell;
294    vertical-align: middle;
295}
296
297#healthIcon {
298    padding-left: 73px;
299    padding-top: 56px;
300    padding-bottom: 56px;
301}
302
303#healthStatusIcon {
304    width: 104px;
305    height: 104px;
306}
307
308#healthText {
309    padding: 50px;
310}
311
312#serviceStatus {
313    font-size: 50px;
314    font-family: BunueloLight;
315    margin-top: 30px;
316}
317
318#healthNote {
319    text-align: left;
320    display: table-cell;
321    vertical-align: middle;
322    padding-left: 43px;
323    line-height: 26px;
324    width: 53%;
325}
326
327table {
328    width: 100%;
329    font-size: 14px;
330    text-align: left;
331    border-collapse: collapse;
332}
333
334th {
335    height: 63px;
336    padding-left: 41px;
337    font-size: 16px;
338}
339
340tr {
341    height: 45px;
342}
343
344td {
345    padding-left: 41px;
346}
347
348/* tag::systemLoadsTableClasses[] */
349.systemLoadsTable {
350    border-collapse:collapse;
351}
352
353.systemLoadsTableHeader {
354    background: #D6D9E4;
355}
356
357.systemLoadsTableOddRow {
358    background: white;
359}
360
361.systemLoadsTableEvenRow {
362    background: #EEEFF3;
363}
364/* end::systemLoadsTableClasses[] */
365
366.sourceRow a {
367    font-weight: 500;
368}
369
370#licenseLink {
371  color: #5E6B8D;
372  text-align: left;
373}
374
375#footer_text {
376    margin-top: 4px;
377    margin-bottom: 4px;
378    font-size: 16px;
379}
380
381#footer_copyright {
382  font-size: 11px;
383}
384
385.refreshSection {
386    padding-top: 20px;
387    padding-left: 160px;
388    color: #3A73B4;
389}
390
391label {
392    margin-right: 20px;
393}
394
395/* FOOTER */
396footer {
397    clear: both;
398    padding-top: 25px;
399}
400
401.footer_container {
402    width: 80%;
403    margin: 0 auto;
404    padding-left: 20px;
405}
406
407#footer_gray_background {
408    background: #F7F8F8;
409}
410
411#footer_top_container #footer_open_liberty {
412    width: 150px;
413    padding: 25px 25px 10px 25px;
414    background-image: url('#{resource["img/small_logo_dark_gray.svg"]}');
415    background-repeat: no-repeat;
416    transition: background-image 0.2s;
417    float: left;
418    clear: left;
419}
420#footer_top_container #footer_project_container {
421    padding-top: 18px;
422    overflow: hidden;
423    display: inline-block;
424}
425#footer_top_container #footer_project {
426    font-weight: 500;
427    font-size: 11px;
428    color: #6A7070;
429    letter-spacing: 0;
430    float: left;
431    clear: left;
432}
433#footer_top_container #footer_round_links_container {
434    margin: 23px 0;
435    float: right;
436}
437#footer_top_container .footer_round_btn {
438    display: inline-block;
439    width: 45px;
440    height: 45px;
441    float: left;
442    background-repeat: no-repeat;
443    background-size: cover;
444}
445#footer_top_container #footer_github_link {
446    background-image: url('#{resource["img/Footer_GitCat.svg"]}');
447}
448#footer_top_container #footer_github_link:hover {
449   background-image: url('#{resource["img/Footer_GitCat_Hover.svg"]}');
450}
451#footer_top_container #footer_stackoverflow_link {
452   background-image: url('#{resource["img/Footer_StackO.svg"]}');
453}
454#footer_top_container #footer_stackoverflow_link:hover {
455    background-image: url('#{resource["img/Footer_StackO_Hover.svg"]}');
456
457}
458#footer_top_container #footer_groupsio_link {
459    background-image: url('#{resource["img/Footer_GroupsIO.svg"]}');
460}
461#footer_top_container #footer_groupsio_link:hover {
462   background-image: url('#{resource["img/Footer_GroupsIO_Hover.svg"]}');
463}
464#footer_top_container #footer_gitter_link {
465    background-image: url('#{resource["img/footer_gitter.svg"]}');
466}
467#footer_top_container #footer_gitter_link:hover {
468    background-image: url('#{resource["img/footer_gitter_hover.svg"]}');
469}
470#footer_top_container .footer_round_btn + .footer_round_btn {
471    margin-left: 21px;
472}
473
474#footer_bottom_container {
475    color: #24253A;
476    margin-bottom: 40px;
477    padding-top: 8px;
478}
479#footer_bottom_container #footer_bottom_left_section, #footer_bottom_container #footer_bottom_right_section {
480   display: inline-block;
481}
482#footer_bottom_container #footer_bottom_right_section {
483    clear: both;
484    float: right;
485}
486#footer_bottom_container #footer_copyright,
487#footer_bottom_container .left_footer_link,
488#footer_bottom_container .vertical_bar {
489    display: inline-block;
490    text-align: left;
491    font-size: 11px;
492    color: #6F7878;
493}
494#footer_bottom_container .vertical_bar {
495    margin: 0 4px;
496}
497#footer_bottom_container .right_footer_link {
498    font-size: 14px;
499    float: left;
500}
501#footer_bottom_container #footer_support_link, #footer_bottom_container #footer_blog_link {
502    margin-left: 28px;
503}
504
505@media (max-width: 991.98px) {
506    #footer_top_container #footer_project {
507        clear: left;
508    }
509    #footer_container {
510       background-image: none;
511    }
512    .right_footer_link {
513        font-size: 14px;
514        float: right;
515    }
516    #footer_support_link, #footer_blog_link {
517        margin-left: 28px;
518    }
519}
520@media (max-width: 767.98px) {
521    #footer_gray_background {
522       height: 131px;
523    }
524    .footer_container {
525        width: 100%;
526    }
527    .right_footer_link {
528       margin-top: 12px;
529    }
530    #footer_open_liberty, #footer_project {
531       display: none;
532    }
533    #footer_bottom_container, #footer_top_container {
534        text-align: center;
535    }
536    #footer_top_container #footer_round_links_container, #footer_bottom_container #footer_bottom_right_section {
537       float: none;
538    }
539    #footer_bottom_container {
540       margin-bottom: 0px;
541    }
542    #footer_round_links_container {
543       display: inline-block;
544    }
545    #footer_bottom_left_section {
546        width: 100%;
547    }
548    #footer_bottom_right_section {
549        position: relative;
550        top: -100px;
551    }
552}
553#teconsent {
554    height: 40px;
555    background-color: rgb(255, 255, 255);
556    z-index: 2147483647;
557    bottom: 0px;
558    right: 0px;
559    margin: 0px;
560    padding: 0px 16px;
561    line-height: 40px;
562    vertical-align: middle;
563    box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 10px 3px;
564    float: right;
565    margin-bottom: 10px;
566    margin-left: 10px;
567}
568
569.blue_link_light_background, .blue_link_light_background:focus {
570    color: #5E6B8D;
571}
572
573.blue_link_light_background:hover {
574    transition: all 0.2s;
575    color: #3F4659;
576}

The index.xhtml uses an h:commandButton tag to create the refresh button. When the button is clicked, the #{systemLoadBean.fetchSystemLoad} action invokes the fetchSystemLoad() method using Jakarta Expression Language. This expression references the systemLoadBean managed bean, triggering the method to update the system load data. The f:ajax tag ensures that the systemLoadForm component is re-rendered without requiring a full page reload.

The systemLoadsTable is populated using the h:dataTable tag, which iterates over the list of system load data provided by the systemLoadBean. The #{systemLoadBean.systemLoads} expression calls the getSystemLoads() method from the managed bean, binding the data to the UI components. If the systemLoadBean isn’t created yet, it is automatically initialized at this point. For each entry, the time, cpuLoad, and memoryUsage fields are displayed by using the h:outputText tag. The f:convertNumber tag formats cpuLoad to seven decimal places and memoryUsage to two decimal places.

To format the table, set the styleClass, headerClass, and rowClasses attributes in the h:dataTable tag. The style elements are defined in the src/main/webapp/resources/css/styles.css file.

Running the application

server.xml

 1<server description="Sample Liberty server">
 2
 3    <featureManager>
 4        <platform>jakartaee-10.0</platform>
 5        <!-- tag::faces[] -->
 6        <feature>faces</feature>
 7        <!-- end::faces[] -->
 8        <!-- tag::expressionLanguage[] -->
 9        <feature>expressionLanguage</feature>
10        <!-- end::expressionLanguage[] -->
11        <!-- tag::cdi[] -->
12        <feature>cdi</feature>
13        <!-- end::cdi[] -->
14    </featureManager>
15
16    <variable name="http.port" defaultValue="9080"/>
17    <variable name="https.port" defaultValue="9443"/>
18
19    <httpEndpoint id="defaultHttpEndpoint" host="*"
20                  httpPort="${http.port}"
21                  httpsPort="${https.port}" />
22
23    <webApplication location="guide-jakarta-faces.war" contextRoot="/"/>
24
25</server>

The required faces, expressionLanguage, and cdi features are enabled for you in the Liberty server.xml configuration file.

Because you started the Open Liberty in dev mode at the beginning of the guide, all the changes were automatically picked up.

Navigate to the http://localhost:9080/index.xhtml URL to view your web application. Click on the refresh icon refresh button to trigger an update on the system loads table.

Testing the application

While you can manually verify the web application by visiting http://localhost:9080/index.xhtml, automated tests are a much better approach because they are more reliable and trigger a failure if a breaking change is introduced. You can write unit tests for your CDI bean to ensure that the basic operations you implemented function correctly.

Create the SystemLoadBeanTest class.
src/test/java/io/openliberty/guides/bean/SystemLoadBeanTest.java

SystemLoadBeanTest.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2024 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.bean;
13
14import static org.junit.jupiter.api.Assertions.assertEquals;
15import static org.junit.jupiter.api.Assertions.assertFalse;
16import static org.junit.jupiter.api.Assertions.assertNotNull;
17
18import org.junit.jupiter.api.BeforeEach;
19import org.junit.jupiter.api.Test;
20
21import io.openliberty.guides.bean.model.SystemLoadData;
22
23public class SystemLoadBeanTest {
24
25    private SystemLoadBean systemLoadBean;
26
27    // tag::BeforeEach[]
28    @BeforeEach
29    // end::BeforeEach[]
30    // tag::setUp[]
31    public void setUp() {
32        systemLoadBean = new SystemLoadBean();
33        systemLoadBean.init();
34    }
35    // end::setUp[]
36
37    @Test
38    // tag::testInitMethod[]
39    public void testInitMethod() {
40        assertNotNull(systemLoadBean.getSystemLoads(),
41                      "System loads should not be null after initialization");
42        assertFalse(systemLoadBean.getSystemLoads().isEmpty(),
43                    "System loads should not be empty after initialization");
44    }
45    // end::testInitMethod[]
46
47    @Test
48    // tag::testFetchSystemLoad[]
49    public void testFetchSystemLoad() {
50        int initialSize = systemLoadBean.getSystemLoads().size();
51        systemLoadBean.fetchSystemLoad();
52        int newSize = systemLoadBean.getSystemLoads().size();
53        assertEquals(initialSize + 1, newSize,
54                     "System loads size should increase by 1 after fetching new data");
55    }
56    // end::testFetchSystemLoad[]
57
58    @Test
59    // tag::testDataIntegrity[]
60    public void testDataIntegrity() {
61        systemLoadBean.fetchSystemLoad();
62        SystemLoadData data = systemLoadBean.getSystemLoads().get(0);
63        assertNotNull(data.getTime(), "Time should not be null");
64        assertNotNull(data.getCpuLoad(), "Recent load should not be null");
65        assertNotNull(data.getMemoryUsage(), "Memory usage should not be null");
66    }
67    // end::testDataIntegrity[]
68}

The setUp() method is annotated with the @BeforeEach annotation, indicating that it is run before each test case to ensure a clean state for each test execution. In this case, it creates a new instance of SystemLoadBean and manually calls the init() method to initialize the list of system load data before each test.

The testInitMethod() test case verifies that after initializing SystemLoadBean, the list of system load data is not null and contains at least one entry.

The testFetchSystemLoad() test case verifies that after calling the fetchSystemLoad() method, the size of the list of system load data increases by one.

The testDataIntegrity() test case verifies that each SystemLoadData entry in the list of system load data contains valid values for time, cpuLoad, and memoryUsage.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

You see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running io.openliberty.guides.bean.SystemLoadBeanTest
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.037 s -- in io.openliberty.guides.bean.SystemLoadBeanTest

Results:

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

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty.

Great work! You’re done!

You just built a dynamic web application on Open Liberty by using Jakarta Faces for the user interface, CDI for managing beans, and Jakarta Expression Language for binding and handling data.

Guide Attribution

Building a dynamic web application with integrated user interface and backend logic by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents
Copied to clipboard

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star