
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.