BLOG POSTS
JSF Tutorial for Beginners: Getting Started

JSF Tutorial for Beginners: Getting Started

JavaServer Faces (JSF) is a component-based framework for building server-side user interfaces in Java web applications. As part of the Java EE ecosystem, JSF simplifies the development of web UIs by providing reusable components, managed beans, and a powerful page navigation system. This tutorial will walk you through the fundamentals of setting up a JSF project, creating your first components, handling user interactions, and implementing common patterns that you’ll encounter in real-world applications.

How JSF Works Under the Hood

JSF follows the Model-View-Controller (MVC) architectural pattern, but with a twist – it’s component-oriented rather than action-oriented like traditional MVC frameworks. The framework operates through a well-defined request processing lifecycle consisting of six phases:

  • Restore View: Reconstructs the component tree from the previous request
  • Apply Request Values: Extracts values from the HTTP request and stores them in components
  • Process Validations: Validates the submitted data against defined validation rules
  • Update Model Values: Updates the backing bean properties with validated values
  • Invoke Application: Executes business logic and navigation
  • Render Response: Generates the HTML response

The component tree is maintained server-side, which allows JSF to track component states across requests. This stateful nature distinguishes JSF from stateless frameworks and enables features like partial page updates and sophisticated event handling.

Setting Up Your First JSF Project

Let’s create a basic JSF application from scratch. You’ll need Java 8+, Maven, and a servlet container like Tomcat or a full Java EE server.

Maven Dependencies

Create a new Maven project and add these dependencies to your pom.xml:

<dependencies>
    <!-- JSF API and Implementation -->
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>jakarta.faces</artifactId>
        <version>3.0.3</version>
    </dependency>
    
    <!-- CDI Implementation -->
    <dependency>
        <groupId>org.jboss.weld.servlet</groupId>
        <artifactId>weld-servlet-shaded</artifactId>
        <version>4.0.3.Final</version>
    </dependency>
    
    <!-- Servlet API -->
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <version>5.0.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

Web Configuration

Configure JSF in your web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/web-app_5_0.xsd"
         version="5.0">
    
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
    
    <welcome-file-list>
        <welcome-file>index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

Creating Your First Managed Bean

Create a simple managed bean using CDI annotations:

package com.example.beans;

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

@Named
@RequestScoped
public class HelloBean {
    
    private String name = "";
    private String message = "";
    
    public void generateGreeting() {
        if (name != null && !name.trim().isEmpty()) {
            message = "Hello, " + name + "! Welcome to JSF.";
        } else {
            message = "Please enter your name.";
        }
    }
    
    // Getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
}

Building the View

Create index.xhtml in your web root:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
    <title>JSF Hello World</title>
</h:head>
<h:body>
    <h1>JSF Tutorial - Hello World</h1>
    
    <h:form>
        <h:panelGrid columns="2">
            <h:outputLabel for="name" value="Enter your name:" />
            <h:inputText id="name" value="#{helloBean.name}" required="true" />
            
            <h:commandButton value="Say Hello" 
                           action="#{helloBean.generateGreeting}" />
            <h:message for="name" style="color: red;" />
        </h:panelGrid>
        
        <h:outputText value="#{helloBean.message}" 
                     style="font-weight: bold; color: green;" 
                     rendered="#{not empty helloBean.message}" />
    </h:form>
</h:body>
</html>

Real-World Use Cases and Examples

Building a User Registration System

Here’s a more practical example showing form validation, custom validators, and navigation:

@Named
@ViewScoped
public class UserRegistrationBean implements Serializable {
    
    private String username;
    private String email;
    private String password;
    private String confirmPassword;
    private Date birthDate;
    private boolean termsAccepted;
    
    public String register() {
        // Validation logic
        if (!password.equals(confirmPassword)) {
            FacesContext.getCurrentInstance().addMessage("registrationForm:confirmPassword",
                new FacesMessage(FacesMessage.SEVERITY_ERROR, 
                    "Password confirmation doesn't match", null));
            return null;
        }
        
        // Business logic - save user
        try {
            userService.createUser(username, email, password, birthDate);
            FacesContext.getCurrentInstance().addMessage(null,
                new FacesMessage(FacesMessage.SEVERITY_INFO, 
                    "Registration successful!", null));
            return "login?faces-redirect=true";
        } catch (UserAlreadyExistsException e) {
            FacesContext.getCurrentInstance().addMessage("registrationForm:username",
                new FacesMessage(FacesMessage.SEVERITY_ERROR, 
                    "Username already exists", null));
            return null;
        }
    }
    
    // Custom validator for username availability
    public void validateUsername(FacesContext context, UIComponent component, Object value) {
        String username = (String) value;
        if (userService.isUsernameTaken(username)) {
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
                "Username is already taken", null));
        }
    }
}

Data Tables with CRUD Operations

JSF excels at displaying and manipulating tabular data:

@Named
@ViewScoped
public class EmployeeListBean implements Serializable {
    
    private List<Employee> employees;
    private Employee selectedEmployee;
    private boolean showEditDialog;
    
    @PostConstruct
    public void init() {
        loadEmployees();
    }
    
    public void loadEmployees() {
        employees = employeeService.getAllEmployees();
    }
    
    public void editEmployee(Employee employee) {
        selectedEmployee = employee.clone(); // Avoid direct binding
        showEditDialog = true;
    }
    
    public void saveEmployee() {
        employeeService.updateEmployee(selectedEmployee);
        loadEmployees();
        showEditDialog = false;
        FacesContext.getCurrentInstance().addMessage(null,
            new FacesMessage("Employee updated successfully"));
    }
    
    public void deleteEmployee(Employee employee) {
        employeeService.deleteEmployee(employee.getId());
        loadEmployees();
        FacesContext.getCurrentInstance().addMessage(null,
            new FacesMessage("Employee deleted successfully"));
    }
}

JSF vs Alternative Frameworks

Feature JSF Spring MVC Struts 2
Architecture Component-based Action-based Action-based
State Management Stateful (server-side) Stateless Stateless
AJAX Support Built-in with f:ajax Manual integration Plugin required
Learning Curve Steep Moderate Moderate
Performance Good for complex UIs Excellent Good
Community Size Medium Large Declining

Performance Considerations and Best Practices

View State Optimization

JSF maintains component state between requests, which can consume significant memory. Here are optimization strategies:

<!-- Configure state saving in web.xml -->
<context-param>
    <param-name>jakarta.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
</context-param>

<!-- Enable state compression -->
<context-param>
    <param-name>jakarta.faces.COMPRESS_STATE</param-name>
    <param-value>true</param-value>
</context-param>

Bean Scope Selection

  • @RequestScoped: Use for simple forms and stateless operations
  • @ViewScoped: Ideal for complex forms with AJAX interactions
  • @SessionScoped: For user session data, shopping carts
  • @ApplicationScoped: For application-wide configuration data

AJAX Best Practices

Leverage partial page updates for better user experience:

<h:form>
    <h:inputText id="searchTerm" value="#{searchBean.term}">
        <f:ajax event="keyup" 
                listener="#{searchBean.search}" 
                render="results" 
                delay="300" />
    </h:inputText>
    
    <h:panelGroup id="results">
        <ui:repeat value="#{searchBean.results}" var="result">
            <div>#{result.name}</div>
        </ui:repeat>
    </h:panelGroup>
</h:form>

Common Pitfalls and Troubleshooting

ViewExpiredException

This occurs when the server-side view state is lost. Handle it gracefully:

@Component
public class ViewExpiredExceptionHandler extends ExceptionHandlerWrapper {
    
    private ExceptionHandler wrapped;
    
    public ViewExpiredExceptionHandler(ExceptionHandler wrapped) {
        this.wrapped = wrapped;
    }
    
    @Override
    public void handle() throws FacesException {
        Iterator<ExceptionQueuedEvent> iterator = getUnhandledExceptionQueuedEvents().iterator();
        
        while (iterator.hasNext()) {
            ExceptionQueuedEvent event = iterator.next();
            ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
            Throwable throwable = context.getException();
            
            if (throwable instanceof ViewExpiredException) {
                FacesContext fc = FacesContext.getCurrentInstance();
                NavigationHandler nav = fc.getApplication().getNavigationHandler();
                nav.handleNavigation(fc, null, "login?faces-redirect=true");
                fc.renderResponse();
                iterator.remove();
            }
        }
        
        getWrapped().handle();
    }
}

Memory Leaks with ViewScoped Beans

Always implement Serializable for ViewScoped beans and be careful with large object graphs:

@Named
@ViewScoped
public class DataTableBean implements Serializable {
    
    private transient SomeNonSerializableService service; // Use transient
    
    @PostConstruct
    public void init() {
        if (service == null) {
            service = CDI.current().select(SomeNonSerializableService.class).get();
        }
    }
}

Component ID Conflicts

Use naming containers to avoid ID conflicts in dynamic content:

<ui:repeat value="#{items}" var="item" varStatus="status">
    <h:panelGroup id="itemContainer#{status.index}">
        <h:inputText id="itemValue" value="#{item.value}" />
        <h:commandButton value="Update" 
                        action="#{bean.updateItem(item)}">
            <f:ajax render="itemContainer#{status.index}" />
        </h:commandButton>
    </h:panelGroup>
</ui:repeat>

Integration with Modern Development Tools

JSF integrates well with modern Java development ecosystems. Consider using PrimeFaces for rich UI components, or OmniFaces for utility functions. For testing, combine Arquillian with JSFUnit for integration testing of your JSF components.

The framework also supports CDI events for loose coupling between components, and can be enhanced with microservices architectures using JAX-RS for REST endpoints alongside your JSF views.

For comprehensive documentation and advanced topics, visit the official Jakarta Faces documentation and explore the Java EE Tutorial for deeper integration patterns.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

Your email address will not be published. Required fields are marked