JavaServer Faces (JSF) – Concepts Overview
Lifecycle, annotations, composite components, CDI scopes, and a Facelets example, all on one page.
1. JSF Lifecycle (6 Phases)
Every request generally passes through six phases. If a conversion/validation error occurs, or if responseComplete() or renderResponse() is called, JSF jumps directly to Render Response.
FacesContext.responseComplete()/renderResponse() is called), JSF skips the remaining phases and renders immediately with the error messages.
Brief description of the phases
| Phase | What happens |
|---|---|
| 1. Restore View | The component tree of the requested view is built from the saved state (or newly created). On an initial request a new view is created; on a postback, the saved view state is restored. |
| 2. Apply Request Values | The values submitted via HTTP are applied to the respective UIComponents as a "local value" (submitted value), without yet writing them into bean properties. |
| 3. Process Validations | For each component, the submitted string is converted into a Java object via a converter and then checked against the registered validators. On errors, FacesMessages are generated and JSF jumps straight to Render Response. |
| 4. Update Model Values | The converted and validated values are actually written into the bean properties bound via EL (the setters are called). |
| 5. Invoke Application | Action methods (e.g. a button click) and action listeners are executed; the result determines, via navigation, the next view to display. |
| 6. Render Response | The (possibly new) component tree is returned to the client as HTML; the view state is saved for the next postback. |
2. Important Annotations
| Annotation | Origin | Purpose |
|---|---|---|
@Named | CDI | Makes a class addressable as a CDI bean via Expression Language (#{beanName}). The standard replacement for the old @ManagedBean. |
@ManagedBean | JSF (legacy) | The old, JSF-native bean management (javax.faces.bean). Deprecated since JSF 2.3 – today use @Named + CDI instead. |
@RequestScoped | CDI | The bean instance lives only for the duration of a single HTTP request. |
@ViewScoped | CDI (jakarta.faces.view) | The bean lives as long as the current view is displayed – it survives multiple Ajax postbacks and is destroyed when navigating to another page. |
@SessionScoped | CDI | One instance per user session, until logout/session timeout, e.g. for login information, language settings, or a shopping cart. |
@ApplicationScoped | CDI | Exactly one instance for the entire application, shared by all users (e.g. for caches/configuration). |
@ConversationScoped | CDI | The bean lives across multiple views until the conversation is explicitly ended (typical for multi-step wizards). |
@Inject | CDI | Injects another bean or dependency into a field, constructor, or setter. |
@ManagedProperty | JSF (legacy) | Injects values or other managed beans into a legacy JSF bean. Replaced by @Inject. |
@FacesConverter | JSF | Marks a class as a custom converter that converts between the display string and a Java object. |
@FacesValidator | JSF | Marks a class as a custom validator for checking input values. |
@FacesComponent | JSF | Defines a custom UIComponent class to extend the component tree. |
@FacesRenderer | JSF | Defines a custom renderer that determines how a component is rendered as HTML. |
@FacesBehavior | JSF | Defines a custom client behavior, e.g. to extend <f:ajax>. |
Note: In newer Jakarta EE versions the packages live under jakarta.* instead of javax.* – the concepts and names of the annotations stay the same.
3. Composite Component – Code Example
Composite components are custom, reusable Facelets tags that are placed in the /resources folder.
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<composite:interface>
<composite:attribute name="label" required="true" />
<composite:attribute name="value" required="true" />
<composite:attribute name="required" default="false" />
</composite:interface>
<composite:implementation>
<h:outputLabel value="#{cc.attrs.label}" for="input" />
<h:inputText id="input"
value="#{cc.attrs.value}"
required="#{cc.attrs.required}" />
<h:message for="input" style="color:red" />
</composite:implementation>
</html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:my="http://xmlns.jcp.org/jsf/composite/components">
<my:labeledInput label="Name" value="#{userBean.name}" required="true" />
</html>
cc.attrs.xxx accesses, from within the component, the attributes declared via composite:attribute. The namespace path jsf/composite/<foldername> is derived automatically from the subfolder in /resources.
4. CDI Scopes in Detail
@RequestScoped
A new instance on every HTTP request. The simplest, stateless variant – suitable for simple forms/list views without state across multiple requests.
@ViewScoped
Persists as long as the user stays on the same page – even across multiple Ajax postbacks. Destroyed when navigating to another view. Ideal for forms with multiple Ajax interactions (e.g. selecting a table row, multi-step input forms on one page).
@SessionScoped
One instance per user session, until logout/session timeout. Typical for login information, language settings, or a shopping cart.
@ApplicationScoped
Exactly one instance, shared by all users and sessions. For global configuration, reference data, or caches. Must be thread-safe, since it's used concurrently by multiple users.
@ConversationScoped
Must be actively started (conversation.begin()) and ended (conversation.end()). It then survives across multiple views – useful for multi-page wizards where @ViewScoped would be too short-lived and @SessionScoped too long-lived.
@Dependent
The CDI default scope if none is specified. A new instance is created on every injection, with its lifecycle tied to the injecting object – no shared state.
Practical tip: For classic JSF pages with forms and Ajax, @ViewScoped combined with @Named is by far the most common case, since the form state must be preserved across multiple Ajax requests without burdening the entire session.
5. JSF Scopes (native)
Before CDI, JSF had its own scope annotations under javax.faces.bean.* (JSF managed beans). Technically, these scopes are simply mapped onto the standard servlet objects. A special case is the flash scope, which exists only in JSF – it doesn't exist in CDI.
| Scope | Storage location (technical) | Special characteristic |
|---|---|---|
@RequestScoped | Attributes of HttpServletRequest | Identical to the CDI counterpart, just a different package origin (javax.faces.bean instead of CDI). |
@ViewScoped | UIViewRoot.getViewMap() – part of the component tree | Originally JSF-specific; was later made CDI-compatible as javax.faces.view.ViewScoped. |
@SessionScoped | Attributes of HttpSession | Identical to the CDI counterpart. |
@ApplicationScoped | Attributes of ServletContext | Identical to the CDI counterpart. |
@NoneScope | – | The bean is not cached; every EL access creates a new instance (no CDI equivalent needed, since @Dependent behaves similarly). |
| Flash scope | ExternalContext.getFlash() – a cookie-based, short-lived attribute | Survives exactly one redirect. Ideal for still showing a success message after a redirect (e.g. save → redirect). Exists only in JSF, not in CDI. |
Flash scope – example
public String save() {
// ... save the order ...
FacesContext.getCurrentInstance()
.getExternalContext()
.getFlash()
.put("message", "Order saved successfully!");
return "orders?faces-redirect=true";
}
<h:outputText value="#{flash.message}" rendered="#{not empty flash.message}"
style="color:green" />
Historical context: JSF 1.x only had XML configuration in faces-config.xml (<managed-bean>), JSF 2.x introduced its own annotations, and with CDI integration (the standard today) beans are managed via @Named + CDI scopes. Native JSF scope annotations have been deprecated since JSF 2.3.
7. Error Handling – Example
JSF applications handle errors at several levels: field validation, programmatic error messages, global exception handling, and Ajax errors.
1. Field/validation errors (standard case)
Already covered by converters/validators in phase 3 of the lifecycle (see sections 1 and 9) – output via <h:message> or <h:messages>.
2. Programmatic error message in the bean
public String save() {
if (orderService.isDuplicate(order)) {
FacesContext.getCurrentInstance().addMessage("orderForm:orderNumber",
new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Order number already exists", null));
return null; // show the page again, the message appears
}
return "confirmation?faces-redirect=true";
}
3. Global exception handling (unexpected errors)
Unhandled exceptions in the lifecycle can be caught centrally via a custom ExceptionHandler, e.g. to redirect to an error page instead of showing a stack trace:
public class CustomExceptionHandler extends ExceptionHandlerWrapper {
private final ExceptionHandler wrapped;
public CustomExceptionHandler(ExceptionHandler wrapped) { this.wrapped = wrapped; }
@Override
public ExceptionHandler getWrapped() { return wrapped; }
@Override
public void handle() throws FacesException {
Iterator<ExceptionQueuedEvent> events = getUnhandledExceptionQueuedEvents().iterator();
while (events.hasNext()) {
Throwable t = events.next().getContext().getException();
try {
FacesContext fc = FacesContext.getCurrentInstance();
fc.getExternalContext().getRequestMap().put("errorMessage", t.getMessage());
fc.getApplication().getNavigationHandler()
.handleNavigation(fc, null, "/error?faces-redirect=true");
fc.renderResponse();
} finally {
events.remove(); // mark as handled
}
}
getWrapped().handle();
}
}
public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) { super(parent); }
@Override
public ExceptionHandler getExceptionHandler() {
return new CustomExceptionHandler(getWrapped().getExceptionHandler());
}
}
<!-- faces-config.xml -->
<factory>
<exception-handler-factory>
com.example.CustomExceptionHandlerFactory
</exception-handler-factory>
</factory>
4. Error pages for unhandled exceptions/HTTP codes (web.xml)
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error.xhtml</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/notfound.xhtml</location>
</error-page>
5. Error handling for Ajax requests
A server-side error during an Ajax postback triggers no visible feedback by default. A JavaScript callback can be registered via onerror:
<h:commandButton value="Save" action="#{orderBean.save}">
<f:ajax execute="@form" render="@form" onerror="handleAjaxError" />
</h:commandButton>
<script>
function handleAjaxError(data) {
if (data.status === "error") {
alert("An error occurred: " + data.description);
}
}
</script>
8. Facelets & Expression Language
What are Facelets?
Since JSF 2.0, Facelets has been the standard view technology (replacing the older JSP). Pages are written as plain XHTML and enriched with JSF tag libraries. Key characteristics: templating (master pages, reusable fragments), composite components, compilation of the XHTML into a component tree at runtime, and – since it's "just" XHTML – good preview/editor support, because the file remains syntactically valid HTML even without server-side rendering.
Expression Language (EL)
EL is used to bind component attributes to bean properties or methods. JSF/Facelets uses the curly-brace syntax #{...} for this (deferred evaluation – can be both read and written, e.g. for value bindings). The ${...} syntax (immediate evaluation, read-only) comes from JSP/JSTL and should be avoided in Facelets.
| Expression | Meaning |
|---|---|
#{bean.property} | Value expression: reads/writes a bean property (getter/setter). |
#{bean.doSomething} | Method expression: calls a parameterless method, e.g. as an action. |
#{bean.doSomething(param)} | Method expression with parameters (since EL 2.2). |
#{not empty bean.list} | Operators: and, or, not, empty, comparisons such as ==, gt, lt. |
#{facesContext}, #{view}, #{request}, #{session}, #{application}, #{flash}, #{resource}, #{cc} | Implicit objects that EL provides without further declaration (#{cc} only inside a composite component). |
Tag libraries
Facelets pages include several standard tag libraries via XML namespace:
| Prefix | Namespace | Purpose / example tags |
|---|---|---|
h: | jsf/html | HTML render components: h:form, h:inputText, h:commandButton, h:dataTable, h:message. |
f: | jsf/core | Non-visual core tags: f:convertDateTime, f:validateLongRange, f:ajax, f:param, f:facet, f:viewParam. |
ui: | jsf/facelets | Templating: ui:composition, ui:insert, ui:define, ui:decorate, ui:include, ui:param. |
composite: | jsf/composite | Building custom composite components: composite:interface, composite:implementation, composite:attribute. |
c: | jsp/jstl/core | JSTL tags such as c:forEach, c:if – act at page build time, not in the component tree; useful for conditionally not creating parts of the page at all. |
p:, o: … | Third-party | Component libraries such as PrimeFaces (p:) or OmniFaces (o:) add extra components/utilities. |
Using Ajax
<f:ajax> makes virtually any JSF component Ajax-capable without having to write JavaScript. Important attributes:
| Attribute | Meaning |
|---|---|
execute | Which components' (values) are sent to the server and processed (@this, @form, @all, @none, or specific IDs). |
render | Which parts of the component tree are re-rendered after the response. |
event | The triggering DOM/JSF event, e.g. click, blur, valueChange (the default depends on the component). |
listener | An optional bean method that is called when triggered (in addition to the normal action). |
onevent / onerror | JavaScript callbacks for successful or failed Ajax responses, respectively. |
Possible values for execute and render
execute controls which components are included in processing (phases 2–4 of the lifecycle) before the request; render controls which parts of the component tree are updated in the browser DOM after the response (phase 6). Both attributes accept the same syntax: reserved @ keywords, concrete client IDs, or a space-separated combination of both.
| Value | Meaning |
|---|---|
@this | Only the component that f:ajax is attached to. Default for execute. |
@none | Nothing at all. For execute this means: no values are processed/updated. Default for render – so by default nothing is re-rendered unless specified explicitly (except implicitly triggered error messages). |
@form | The enclosing <h:form> of the component – by far the most common value, since it processes/updates all fields of a form. |
@all | The entire view (the whole page) – for render this is effectively a "full Ajax reload" of the body; for execute, all form values on the page are processed. |
| Concrete client ID(s) | One or more space-separated IDs, e.g. render="dobMsg ageMsg". IDs can be given relatively (within the same NamingContainer) or absolutely with a leading colon, e.g. :mainForm:summaryPanel. |
@namingcontainer | (since JSF 2.3) The nearest enclosing NamingContainer (e.g. an h:panelGroup with its own id, or a composite component) – handy for rendering relative to "my container" without hard-wiring its ID. |
@composite | (since JSF 2.3) The enclosing composite component, if the current component is located inside one. |
@parent | (since JSF 2.3) The direct parent component in the component tree. |
@child(n) | (since JSF 2.3) The n-th child (0-based) of the current component. |
@previous / @next | (since JSF 2.3) The previous or next sibling component at the same level, respectively. |
@id(id) | (since JSF 2.3) Looks up a component with this id regardless of its position in the tree – useful when the relative structure is unknown/variable. |
Examples: render="@form" updates the entire form (e.g. after submitting with possible validation errors in several fields);
render="@this" updates only the triggering component itself (e.g. a counter label);
render="dobMsg ageMsg summaryPanel" updates several individual areas in a targeted way;
render="@none" (or omitting it) suppresses any DOM update when only a listener method should run in the background.
If a given ID isn't found, most implementations silently ignore that part of the specification instead of throwing an error – so when debugging "why isn't my panel updating", it's always worth checking for typos in the ID or the correct NamingContainer prefix.
<h:selectOneMenu value="#{orderBean.country}">
<f:selectItems value="#{orderBean.countries}" />
<f:ajax listener="#{orderBean.onCountryChange}" render="citySelect" />
</h:selectOneMenu>
<h:selectOneMenu id="citySelect" value="#{orderBean.city}">
<f:selectItems value="#{orderBean.citiesForSelectedCountry}" />
</h:selectOneMenu>
9. Concrete Facelets Example
A template with ui:insert, a page that uses it via ui:composition and ui:define, and a form with a converter, validator, and Ajax.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title><ui:insert name="title">My App</ui:insert></title>
</h:head>
<h:body>
<div id="header"><ui:insert name="header">Default header</ui:insert></div>
<div id="content"><ui:insert name="content" /></div>
<div id="footer">© 2026 – My App</div>
</h:body>
</html>
<ui:composition template="/WEB-INF/templates/layout.xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<ui:define name="title">Registration</ui:define>
<ui:define name="content">
<h:form>
<h:panelGrid columns="3">
<h:outputLabel value="Date of birth" for="dob" />
<h:inputText id="dob" value="#{registerBean.birthDate}">
<f:convertDateTime pattern="dd.MM.yyyy" />
<f:ajax event="blur" render="dobMsg" />
</h:inputText>
<h:message id="dobMsg" for="dob" style="color:red" />
<h:outputLabel value="Age" for="age" />
<h:inputText id="age" value="#{registerBean.age}">
<f:validateLongRange minimum="18" maximum="120" />
<f:ajax event="blur" render="ageMsg" />
</h:inputText>
<h:message id="ageMsg" for="age" style="color:red" />
</h:panelGrid>
<h:commandButton value="Submit" action="#{registerBean.submit}" />
</h:form>
</ui:define>
</ui:composition>
@Named
@ViewScoped
public class RegisterBean implements Serializable {
private LocalDate birthDate;
private int age;
public String submit() {
// business logic, e.g. save to DB
return "success?faces-redirect=true";
}
// getters & setters
public LocalDate getBirthDate() { return birthDate; }
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
Lifecycle flow: On submit, the strings from dob/age are applied in phase 2, converted/validated in phase 3 via f:convertDateTime and f:validateLongRange respectively
(on error → straight to Render Response with h:message), written into the bean properties in phase 4,
submit() is called in phase 5, and the redirect to the next page happens in phase 6.
10. Showing and Hiding UI Elements
There are three common approaches in JSF for showing/hiding sections – each with different trade-offs in server traffic, security, and responsiveness.
| Variant | Server round trip | Characteristic |
|---|---|---|
A: rendered without Ajax | Yes (full postback) | With rendered="false" the element is not written into the HTML at all. The entire page is reloaded. |
| B: purely client-side (CSS/JS) | No | Immediate reaction without server contact, but the element stays in the DOM (only hidden) – not suitable for sensitive content. |
C: rendered with Ajax | Yes (only a partial area) | Like A, but only the affected panel area is re-rendered – no full page reload, better UX. |
A: Without Ajax – server-side via rendered
The classic approach: a boolean field in the bean controls the rendered attribute. Every click triggers a full postback, and the entire page is rebuilt and re-rendered.
<h:form>
<h:selectBooleanCheckbox value="#{formBean.showDetails}" />
<h:outputLabel value="Show additional info" />
<h:commandButton value="Refresh" action="#{formBean.refresh}" />
<h:panelGroup rendered="#{formBean.showDetails}" layout="block">
<h:outputLabel value="Comment" for="comment" />
<h:inputTextarea id="comment" value="#{formBean.details}" />
</h:panelGroup>
</h:form>
@Named
@ViewScoped
public class FormBean implements Serializable {
private boolean showDetails;
private String details;
public String refresh() { return null; } // no navigation target, just a postback
// getters & setters
}
B: Without Ajax – purely client-side (no server contact)
If no server data is involved, plain JavaScript/CSS is enough – faster, but the element stays (hidden) in the DOM and is still transferred to the client.
<h:commandButton type="button" value="Show/hide details"
onclick="var p = document.getElementById('detailsPanel');
p.style.display = (p.style.display === 'none') ? 'block' : 'none';" />
<h:panelGroup id="detailsPanel" layout="block" style="display:none">
<h:outputText value="This additional info is only shown/hidden in the browser." />
</h:panelGroup>
type="button" prevents the button from submitting a form. Since no rendered logic applies here, the content always remains present in the component tree and in the HTML.
C: With Ajax – server-side via rendered + f:ajax
Combines the best of both worlds: the state is still controlled server-side via the bean (so with rendered="false" the element really isn't in the HTML), but only the enclosing area is re-rendered – no page reload.
<h:form>
<h:selectBooleanCheckbox value="#{formBean.showDetails}">
<f:ajax render="detailsPanel" />
</h:selectBooleanCheckbox>
<h:outputLabel value="Show additional info" />
<h:panelGroup id="detailsPanel" layout="block">
<h:panelGrid columns="2" rendered="#{formBean.showDetails}">
<h:outputLabel value="Comment" for="comment" />
<h:inputTextarea id="comment" value="#{formBean.details}" />
</h:panelGrid>
</h:panelGroup>
</h:form>
Flow: h:selectBooleanCheckbox triggers an Ajax request on change by default; showDetails is updated in phase 4 (Update Model Values),
and phase 6 (Render Response) then returns only the detailsPanel, since that's exactly what was requested via render.
An additional listener isn't needed here, since the value binding alone is enough to control visibility.
11. Bean Validation (JSR 380)
Instead of declaring validation via f:validate* tags in the view, rules can be annotated directly on the model (Jakarta Bean Validation, formerly JSR 303/380). By default, JSF automatically invokes a BeanValidator for this in phase 3 (Process Validations) – with no extra configuration needed.
public class UserDto {
@NotNull
@Size(min = 2, max = 50)
private String name;
@NotNull
@Email
private String email;
@Min(18)
private int age;
// getters & setters
}
@Named
@ViewScoped
public class RegisterBean implements Serializable {
private final UserDto user = new UserDto();
public String submit() {
// only reached if all bean validation constraints are satisfied
return "success?faces-redirect=true";
}
public UserDto getUser() { return user; }
}
<h:form>
<h:inputText value="#{registerBean.user.name}" />
<h:message for="..." />
<h:inputText value="#{registerBean.user.email}" />
<h:inputText value="#{registerBean.user.age}">
<f:validateBean disabled="false" />
</h:inputText>
<h:commandButton value="Register" action="#{registerBean.submit}" />
</h:form>
With <f:validateBean validationGroups="..."/> you can selectively activate validation groups, and with disabled="true" you can turn off automatic bean validation for individual fields (e.g. when only a custom validator should run there).
12. Custom Converter & Validator – Code Example
Custom converter
A converter converts between the display string in the browser and a Java object – here's an example that resolves a product ID into a full Product object (useful e.g. in h:selectOneMenu).
@FacesConverter(value = "productConverter", managed = true)
public class ProductConverter implements Converter<Product> {
@Inject
private ProductService productService;
@Override
public Product getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isBlank()) return null;
return productService.findById(Long.valueOf(value));
}
@Override
public String getAsString(FacesContext context, UIComponent component, Product product) {
return product == null ? "" : String.valueOf(product.getId());
}
}
<h:selectOneMenu value="#{orderBean.selectedProduct}" converter="productConverter">
<f:selectItems value="#{orderBean.availableProducts}" var="p"
itemLabel="#{p.name}" itemValue="#{p}" />
</h:selectOneMenu>
managed = true ensures that CDI injection (here @Inject ProductService) works inside the converter – without this attribute, JSF would instantiate the converter itself, without involving CDI.
Custom validator
@FacesValidator(value = "strongPasswordValidator", managed = true)
public class StrongPasswordValidator implements Validator<String> {
@Override
public void validate(FacesContext context, UIComponent component, String value) {
if (value == null || value.length() < 8 || !value.matches(".*[A-Z].*")) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Password must be at least 8 characters long and contain an uppercase letter", null));
}
}
}
<h:inputSecret value="#{registerBean.password}" validator="strongPasswordValidator" />
<h:message for="password" />
13. File Upload
Since JSF 2.2, <h:inputFile> supports file uploads directly as a standard component – for this, the form needs enctype="multipart/form-data".
<h:form enctype="multipart/form-data">
<h:inputFile value="#{uploadBean.file}" />
<h:commandButton value="Upload" action="#{uploadBean.upload}" />
</h:form>
@Named
@RequestScoped
public class UploadBean {
private Part file; // jakarta.servlet.http.Part
public String upload() throws IOException {
try (InputStream in = file.getInputStream()) {
Files.copy(in, Paths.get("/uploads/" + file.getSubmittedFileName()),
StandardCopyOption.REPLACE_EXISTING);
}
return "success?faces-redirect=true";
}
public Part getFile() { return file; }
public void setFile(Part file) { this.file = file; }
}
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<multipart-config>
<max-file-size>10485760</max-file-size> <!-- 10 MB per file -->
<max-request-size>20971520</max-request-size> <!-- 20 MB total -->
</multipart-config>
</servlet>
14. View Parameters & Post-Redirect-Get
<f:viewParam> binds GET query parameters to bean properties – this makes pages bookmarkable and safely repeatable via browser reload, without form data needing to be resubmitted.
<f:metadata>
<f:viewParam name="id" value="#{productBean.productId}" />
<f:viewAction action="#{productBean.loadProduct}" />
</f:metadata>
<h:body>
<h1>#{productBean.product.name}</h1>
<p>#{productBean.product.description}</p>
</h:body>
@Named
@ViewScoped
public class ProductBean implements Serializable {
private Long productId;
private Product product;
public void loadProduct() {
product = productService.findById(productId);
}
// getters & setters
}
<h:link value="Details" outcome="product">
<f:param name="id" value="#{p.id}" />
</h:link>
<!-- generates e.g.: /product.xhtml?id=42 -->
Post-Redirect-Get (PRG): An action method that responds to a POST with "...?faces-redirect=true" (see section 6) prevents a browser reload from resubmitting the form – after the redirect, the browser's history shows only a GET request. With &includeViewParams=true, any existing f:viewParam values are additionally appended to the target URL.
15. Resource Handling
In JSF, static resources (CSS, JavaScript, images) are organized via the /resources folder and included with dedicated tags instead of plain HTML tags. Benefits: automatic versioning/cache-busting, correct content-type headers, and support for "resource libraries" (e.g. theme-dependent swapping of CSS files).
/resources
/css
styles.css
/js
app.js
/images
logo.png
<h:head>
<h:outputStylesheet name="styles.css" library="css" />
<h:outputScript name="app.js" library="js" target="head" />
</h:head>
<h:body>
<h:graphicImage name="logo.png" library="images" alt="Logo" />
</h:body>
JSF serves these resources via its own URL (e.g. /javax.faces.resource/styles.css.xhtml?ln=css) and appends a version stamp if needed, so that browser caches are automatically invalidated when a new version is deployed.
16. Internationalization (i18n)
Multilingual texts are managed via Java ResourceBundles (.properties files) and included in the page via EL.
<application>
<locale-config>
<default-locale>de</default-locale>
<supported-locale>en</supported-locale>
</locale-config>
<resource-bundle>
<base-name>messages</base-name>
<var>msg</var>
</resource-bundle>
</application>
welcomeText=Willkommen bei unserer Anwendung
saveButton=Speichern
# messages_en.properties
welcomeText=Welcome to our application
saveButton=Save
<h:outputText value="#{msg.welcomeText}" />
<h:commandButton value="#{msg.saveButton}" action="#{formBean.save}" />
FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale("en"));
Alternatively, a bundle can also be loaded directly in a page without an entry in faces-config.xml: <f:loadBundle basename="messages" var="msg" />.
17. State Saving & CSRF
JSF has to keep the component tree "somewhere" between two requests for a postback to work. Where that happens is controlled by the state-saving method.
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value> <!-- or: client -->
</context-param>
| Method | Behavior |
|---|---|
| server (default) | The component tree state resides in server memory (bound to the session); the client only receives a reference to it in the hidden javax.faces.ViewState field. Lower client traffic, but memory usage per session and, typically, session affinity is needed in a cluster. |
| client | The entire (serialized, typically encrypted) view state is embedded directly in the javax.faces.ViewState field. The server stays stateless (simpler clustering), but every request transfers more data. |
CSRF protection: The javax.faces.ViewState field is tied to the session or a random view ID and must match exactly on postback – this already hampers classic CSRF attacks out of the box. Modern JSF implementations (Mojarra/MyFaces) additionally randomize the view state ID to avoid predictability.
<f:view transient="true">
...
</f:view>
For pure display pages without forms/postbacks, transient="true" avoids creating a view state entirely – neither server- nor client-side state saving is needed.
18. Faces Flows & PhaseListener
Faces Flows
Faces Flows (since JSF 2.2) encapsulate multi-page processes (e.g. a checkout process) including their own scope – a declarative alternative/complement to @ConversationScoped. A flow is created by convention: a folder with the flow's name that contains a matching <name>-flow.xml.
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
version="2.2">
<flow-definition id="checkout">
<start-node>step1</start-node>
<flow-return id="checkoutComplete">
<from-outcome>/confirmation</from-outcome>
</flow-return>
</flow-definition>
</faces-config>
@Named
@FlowScoped("checkout")
public class CheckoutBean implements Serializable {
private Cart cart;
// lives only as long as the user is within the "checkout" flow
}
<h:link outcome="checkout/step1" value="Checkout" />
PhaseListener
A PhaseListener hooks in globally before/after specific lifecycle phases – useful for logging, monitoring, or cross-cutting concerns that don't belong in individual beans.
public class LoggingPhaseListener implements PhaseListener {
@Override
public void beforePhase(PhaseEvent event) {
System.out.println("Before phase: " + event.getPhaseId());
}
@Override
public void afterPhase(PhaseEvent event) {
System.out.println("After phase: " + event.getPhaseId());
}
@Override
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE; // or specifically e.g. PhaseId.RENDER_RESPONSE
}
}
<lifecycle>
<phase-listener>com.example.LoggingPhaseListener</phase-listener>
</lifecycle>