Ajax
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Jakarta Faces 4.1 (API jakarta.faces-api 4.1.2, part of Jakarta EE 11), Eclipse Mojarra 4.1.17 and
Apache MyFaces Core 4.1.4, with Java 17 or later. The change lists of the 4.1 and 4.0 specifications contain a single Ajax
entry (an improvement of f:ajax inside composite components in 4.0) and no 4.1 entry, so the text also describes Faces 4.0
(nothing was run on 4.0); the earlier releases are in What Changed. The examples were compiled and deployed in a scratch
Bookshelf WAR on Apache Tomcat 11.0.26 with Weld 6.0.4.Final, once with Mojarra and once with MyFaces. The server side was
driven with curl, and the JavaScript side with a real browser (headless Google Chrome controlled through the DevTools
protocol), so the request parameters, the partial responses, the event and error payloads and the timings come from those
runs (checked on 2026-10-08). The places where the two implementations disagree are collected in Mojarra and MyFaces.
Ajax is how a Faces page changes part of itself without a full page load. Faces does it without any hand-written
JavaScript or response format on the application’s side: a behaviour attached to a component (f:ajax) makes the browser send
the form with a marker header, the server runs the usual lifecycle on only the components that the page named, and it answers
with an XML document, the partial response, that tells the browser which pieces of markup to replace. Everything the
framework does here is specified (chapters 13 and 14 of the specification, the JavaScript documentation and the
PartialViewContext API), which is why the same page works on every implementation and with every component library.
The Ajax Model
An Ajax request in Faces is not a different kind of request. It is a normal postback of the page’s form, with three differences:
-
It is sent by
faces.ajax.request(the JavaScript function in thefaces.jsresource) as an asynchronousPOST, with the request headerFaces-Request: partial/ajaxand the request parameterjakarta.faces.partial.ajax=true. The URL is theactionof the form. -
It names which components take part in the execute half of the lifecycle (
jakarta.faces.partial.execute) and which are re-rendered (jakarta.faces.partial.render). Both are lists of client ids, or one of the keywords@alland@none. -
The response is not a page. It is a
text/xmldocument ofupdate,insert,delete,attributes,eval,redirectanderroroperations thatfaces.ajax.responseapplies to the DOM.
There are three ways to start such a request from a view, and all three end in faces.ajax.request:
| Entry point | What it is | Use it when |
|---|---|---|
|
A tag that attaches an |
A component should send a request on one of its own events. This is the normal case. |
|
The JavaScript API, called from any script or event attribute. |
The request is triggered by something that is not a Faces component event, or the options are computed. |
|
A component that renders a named JavaScript function which, when called, sends the request. |
A script (a timer, a callback of a third-party widget, the |
The server side is jakarta.faces.context.PartialViewContext. The tutorial says it captures the state of an Ajax request
(which components are executed, which are rendered), and that its processPartial(PhaseId) method does the partial tree
traversal in each phase. A request is partial when PartialViewContext.isPartialRequest() is true; it is an Ajax request
when isAjaxRequest() is true, which the API documentation defines as the presence of the Faces-Request header with the
value partial/ajax. The same documentation says that isPartialRequest() is also true for the header value
partial/process, a partial processing request that is not Ajax; this page deals with partial/ajax.
A First Example: Live Search in Bookshelf
The catalogue of Bookshelf gets a search box that filters the list while the user types, without a page load. The page below
is shown without the site template. The f:ajax inside the h:inputText sends a request 300 milliseconds after the last
key-up, runs the bean’s search listener, and re-renders the result list and the counter.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:ui="jakarta.faces.facelets">
<h:head>
<title>Search the catalogue</title>
</h:head>
<h:body>
<h:form id="search">
<h:inputText id="q" value="#{searchBean.query}">
<f:ajax event="keyup" delay="300" listener="#{searchBean.search}" render="results count"/>
</h:inputText>
<h:outputText id="count" value="#{searchBean.searches} searches"/>
<h:panelGroup id="results" layout="block">
<ui:repeat value="#{searchBean.results}" var="b">
<div class="book">#{b.title} by #{b.author}</div>
</ui:repeat>
</h:panelGroup>
</h:form>
</h:body>
</html>
package org.example.bookshelf.web;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.List;
import org.example.bookshelf.model.Book;
import org.example.bookshelf.service.BookService;
@Named
@ViewScoped
public class SearchBean implements Serializable {
@Inject
private BookService books;
private String query = "";
private List<Book> results;
private int searches;
public void search(AjaxBehaviorEvent event) {
searches++;
results = books.search(query);
}
public List<Book> getResults() {
return results != null ? results : books.findAll();
}
public int getSearches() { return searches; }
public String getQuery() { return query; }
public void setQuery(String query) { this.query = query; }
}
Typing Dune, Gibson and Le Guin into the field in a browser changed the counter to 1 searches, 2 searches and
3 searches and the list to the matching books, on both implementations. The counter keeps growing because the bean is
@ViewScoped: the same instance serves every Ajax request of the view, which is the scope an Ajax-driven page almost always
wants. A @RequestScoped bean would be created again for every request; that is fine for a listener whose result is rendered
in the same request, as in the review preview below, and wrong for state that must survive until the next request.
Later XHTML listings of this page show only the h:head and h:body part: they sit inside the same html element, with the
same h, f and ui namespace declarations and the DOCTYPE, as this first one.
A Throwaway Bean for the Small Examples
The short examples further down (groups, resetValues, h:commandScript, the listener traces and the programmatic behaviour)
share one view-scoped bean, jsBean, so that each page stays a few lines. It is scaffolding for the experiments, not a
Bookshelf class:
package org.example.bookshelf.web;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.List;
@Named
@ViewScoped
public class JsBean implements Serializable {
@Inject
private FacesContext faces;
private String name = "start";
private String last = "";
private boolean flag;
private boolean visible;
public void refresh() {
last = "refreshed:" + name + ":" + faces.getExternalContext().getRequestParameterMap().get("tag");
}
public void noArg() { last = "noarg-ran"; }
public void phase(AjaxBehaviorEvent event) {
System.out.println("LISTENER " + event.getComponent().getId() + " in " + faces.getCurrentPhaseId() + " name=" + name);
}
public String toggle() { visible = !visible; return null; }
public String go() { return "js?faces-redirect=true"; }
public List<String> getIds() { return List.of("a", "b"); }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLast() { return last; }
public boolean isFlag() { return flag; }
public void setFlag(boolean flag) { this.flag = flag; }
public boolean isVisible() { return visible; }
}
One Request from Keystroke to DOM Update
The numbered steps of the diagram are the ones to keep in mind when something does not update.
The request that the browser actually sent for the live search (decoded, from a request filter in the scratch application; Mojarra 4.1.17):
POST /bookshelf/search.xhtml
Faces-Request: partial/ajax
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
search = search
search:q = Dune
jakarta.faces.ViewState = ...
jakarta.faces.partial.ajax = true
jakarta.faces.partial.event = keyup
jakarta.faces.behavior.event = keyup
jakarta.faces.source = search:q
jakarta.faces.partial.execute = search:q
jakarta.faces.partial.render = search:results search:count
and the answer (Mojarra; the markup inside each CDATA section is the normal rendering of that component):
<?xml version='1.0' encoding='UTF-8'?>
<partial-response><changes>
<update id="search:count"><![CDATA[<span id="search:count">1 searches</span>]]></update>
<update id="search:results"><![CDATA[<div id="search:results">
<div class="book">Dune by Frank Herbert</div>
<div class="book">Dune Messiah by Frank Herbert</div></div>]]></update>
<update id="j_id1:jakarta.faces.ViewState:0"><![CDATA[8277362929392712006:9149938755704541982]]></update>
</changes></partial-response>
Three details of the capture are worth knowing. The render list named results first, but the update for count comes
first: components are rendered in the order of the component tree, not in the order of the list (on both implementations).
The ViewState update is always last and keeps the form’s view state current, so the next request carries a valid one. And the
browser sent only the field that was executed: Mojarra 4.1.17 posts just the controls named in execute (plus the form
marker), while MyFaces 4.1.4 posts every field of the form and the hidden formId_SUBMIT marker. Both are valid, because only
executed components are decoded.
The f:ajax Tag
f:ajax registers an AjaxBehavior on one component, or on every ClientBehaviorHolder component inside it (see
Grouping Components). The tag library is jakarta.faces.core. The attributes, as the VDL documentation lists them:
| Attribute | Value | Meaning |
|---|---|---|
|
space-separated search expressions, or an expression that evaluates to a |
The components that take part in Apply Request Values, Process Validations and Update Model Values. Default |
|
same |
The components rendered in Render Response. Default |
|
a string or an expression |
The client event that sends the request. A DOM event name without the |
|
a method expression |
Called once with an |
|
the name of a JavaScript function |
Called at the |
|
the name of a JavaScript function |
Called when the request or the response fails. See Ajax Error Handling. |
|
milliseconds, or the literal |
If less than this much time passes between two calls, only the last is sent. See delay, Queueing and disabled. |
|
a boolean |
Resets the inputs named in |
|
a boolean |
|
|
a boolean |
|
The execute and render attributes accept search expressions, so the keywords and the relative and absolute client ids are
described together in execute and render.
The event Attribute and the Default Events
f:ajax with no event uses the default of the component it is nested in: action for an ActionSource such as a button or
a link, valueChange for an EditableValueHolder such as an input (the tutorial). The names are logical, and each component
maps them to a DOM event. What the two implementations rendered for the standard components:
| Component | Mojarra 4.1.17 | MyFaces 4.1.4 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Only the browser’s own events are possible beyond the logical ones, and each component accepts a fixed list. getEventNames()
of ClientBehaviorHolder returns it. An unsupported name fails when the view is built, not when the user acts: with
event="onkeyup" on an h:inputText, Mojarra answered with a 500 whose title read 'onkeyup' is not a supported event for
HtmlInputText and listed blur, change, click, dblclick, focus, keydown, keypress, keyup, mousedown, mousemove, mouseout,
mouseover, mouseup, select, valueChange, and MyFaces answered 500 with it is not a valid eventName defined for this
component. The on prefix is the usual mistake.
The standard components that implement ClientBehaviorHolder (the API documentation lists them) include the input,
select and command components, h:form, h:body, h:outputLabel, h:outputLink, h:link, h:button, h:graphicImage,
h:panelGrid, h:panelGroup, h:dataTable, h:inputFile and UIWebsocket (the component behind f:websocket, which
accepts a nested f:ajax). h:outputText is not one; to make a piece of text clickable, wrap it in an h:panelGroup with
event="click":
<h:panelGroup id="rating" layout="block">
<f:ajax event="click" render="details"/>
<h:outputText value="#{book.title}"/>
</h:panelGroup>
execute and render
execute answers "which components does the server process?", render answers "which components does the server send back?".
They are independent: a request can execute one input and render a different panel, execute a whole form and render nothing,
or execute nothing and render a clock.
Keywords
The specification defines four keywords for both attributes. They are resolved in the browser: the handler that the server
renders still contains the keyword, and faces.ajax.request replaces it with client ids before it sends the request.
| Keyword | Meaning |
|---|---|
|
The component that triggered the request (the source). Default of |
|
The form that encloses the source. |
|
The whole view. For |
|
Nothing. Default of |
The search expression framework of Faces 2.3 adds more (@parent, @namingcontainer, @next, @previous, @child(n),
@id(...), custom keywords), and the page on search expressions covers all of them and SearchExpressionHandler. The VDL
documentation of f:ajax says the values are search expressions, so any of them is valid here.
Two things happen between the tag and the wire. At render time the tag handler resolves relative and absolute ids to client
ids: in the review form, render="@form :messages" was rendered as mojarra.ab(this,event,'action','@form','@form messages')
and myfaces.ab(this,event,'action','@form','@form messages',{}), so :messages (absolute, from the view root) became the
client id messages of a component outside the form. And when the user clicks, faces.ajax.request resolves the keywords:
a button with execute="@form" in a form with the id f sent jakarta.faces.partial.execute=f:boom f on Mojarra and f f:boom
on MyFaces, the source’s id and the form’s id. So the source is part of the execute list even when the page names other
components (Mojarra sent f:btn f:name for execute: 'f:name', MyFaces f:name f:btn).
That last fact matters for tests written by hand. A request posted with curl whose partial.execute omits the source
button was processed by Mojarra without ever invoking the button’s f:ajax listener (the phase trace showed
exec=[f:title] and no listener call), while MyFaces invoked it anyway. A raw request that sends the keyword itself (jakarta.faces.partial.execute=@form) fared no
better: neither implementation expanded it on the server, so Mojarra processed nothing at all (no setter ran, no message,
no error) and MyFaces ran the button’s action without the inputs (the action failed on the null rating). Copy what the browser sends.
Review Preview: Execute Several Inputs, Render the Preview
The review form of Bookshelf shows a preview as the user fills in the rating. The rating input executes both inputs (they
belong together), runs the listener and re-renders the preview and the two field messages. A final button submits the whole
form and renders the messages that live outside it.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head>
<title>Review a book</title>
</h:head>
<h:body>
<h:messages id="messages" globalOnly="true"/>
<h:form id="review">
<h:inputText id="reviewer" value="#{reviewBean.reviewer}" required="true"/>
<h:message id="reviewerMsg" for="reviewer"/>
<h:inputText id="rating" value="#{reviewBean.rating}" required="true">
<f:validateLongRange minimum="1" maximum="5"/>
<f:ajax event="change" execute="reviewer rating" listener="#{reviewBean.preview}"
render="summary reviewerMsg ratingMsg"/>
</h:inputText>
<h:message id="ratingMsg" for="rating"/>
<h:outputText id="summary" value="#{reviewBean.summary}"/>
<h:commandButton id="send" value="Send" action="#{reviewBean.submit}">
<f:ajax execute="@form" render="@form :messages"/>
</h:commandButton>
</h:form>
</h:body>
</html>
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.example.bookshelf.service.ReviewService;
@Named
@RequestScoped
public class ReviewBean {
@Inject
private FacesContext faces;
@Inject
private ReviewService reviews;
private String reviewer;
private Integer rating;
private String summary = "";
public void preview(AjaxBehaviorEvent event) {
summary = reviewer + " gives " + rating + " stars";
}
public void submit() {
int number = reviews.save(reviewer, rating);
faces.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Review " + number + " saved", null));
summary = "";
}
public void failing() {
throw new IllegalStateException("The review service is down");
}
public String getReviewer() { return reviewer; }
public void setReviewer(String reviewer) { this.reviewer = reviewer; }
public Integer getRating() { return rating; }
public void setRating(Integer rating) { this.rating = rating; }
public String getSummary() { return summary; }
}
A change request with reviewer=Ann and rating=4 answered with an update for review:summary containing
Ann gives 4 stars and empty updates for the two messages; the phase listener trace showed the request going through all
six phases with ajax=true, exec=[review:reviewer, review:rating] and render=[review:summary, review:reviewerMsg,
review:ratingMsg]. With rating=9 the response carried the range message in review:ratingMsg, an empty
review:summary, and the listener was not called (see The listener and the AjaxBehaviorEvent). The Send button re-renders @form and the absolute
:messages, which is why the saved-message appears even though the h:messages component is not inside the form. If
:messages were left out of render, the message would be added to the FacesContext of that request, never sent to the
browser and lost with the request, which is the most common "my Ajax action does nothing" report.
Ids from an Expression
Both attributes accept a value expression that evaluates to a Collection<String>, for ids that are decided at run time. With a
bean method getIds() returning List.of("a", "b"), the markup
<h:inputText id="a" value="#{jsBean.name}">
<f:ajax event="blur" execute="#{jsBean.ids}" render="#{jsBean.ids}"/>
</h:inputText>
<h:outputText id="b" value="#{jsBean.name}"/>
was rendered with the handler onblur="mojarra.ab(this,event,'blur','f:a f:b','f:a f:b')" (and the MyFaces equivalent):
the collection was evaluated and resolved to client ids when the page was rendered. The expression is therefore evaluated
during Render Response of the page that contains the tag, not when the user acts. A literal value is split on spaces.
What Can Be Rendered
A component can be updated only if its element exists in the page. The browser replaces the contents of the element whose id
matches the update. A component with rendered="false" produces no element, and a later request cannot add it:
<h:form id="f">
<h:commandButton id="show" value="Show" action="#{jsBean.toggle}">
<f:ajax render="ghost wrapper"/>
</h:commandButton>
<h:outputText id="ghost" value="ghost-text" rendered="#{jsBean.visible}"/>
<h:panelGroup id="wrapper" layout="block">
<h:outputText id="inner" value="inner-text" rendered="#{jsBean.visible}"/>
</h:panelGroup>
</h:form>
Rendering ghost, whose element was absent at the first render, failed on Mojarra with the client error malformedXML and
the description During update: f:ghost not found, and the updates after it in the response were not applied (the wrapper
stayed empty). MyFaces skipped the unknown element silently and rendered wrapper with its inner text. The portable form is
the second one: always render an element that is always in the page (wrapper) and let the conditional content live inside
it. The same holds for tables and repeated rows; render the table, not a row that may not exist.
A rendered condition is evaluated on every request, so a component can switch from hidden to visible within the wrapper.
Do not render h:head, h:body and the form element casually either: @all is the supported way to replace the document.
The listener and the AjaxBehaviorEvent
The listener attribute is a method expression with the signature public void processAjaxBehavior(AjaxBehaviorEvent event),
the method of jakarta.faces.event.AjaxBehaviorListener; it may throw AbortProcessingException to stop lifecycle processing.
The tutorial says it is called once, in Invoke Application. The event is queued during Apply Request Values (the behaviour’s
renderer decodes it) and broadcast later, so the listener follows the same rules as an action method: it runs only if the
executed components converted and validated without error and the model has been updated.
The immediate attribute changes only the broadcast phase. A trace of the two variants, with a listener that logs the current
phase and the bean property bound to the executed input:
<h:inputText id="name" value="#{jsBean.name}" required="true"/>
<h:message id="msg" for="name"/>
<h:commandButton id="normal" value="n">
<f:ajax execute="name" render="msg" listener="#{jsBean.phase}"/>
</h:commandButton>
<h:commandButton id="imm" value="i">
<f:ajax execute="name" render="msg" immediate="true" listener="#{jsBean.phase}"/>
</h:commandButton>
normal, name=Zed LISTENER normal in INVOKE_APPLICATION 5 name=Zed
immediate, name=Zed LISTENER imm in APPLY_REQUEST_VALUES 2 name=start
normal, name="" (required fails: the listener is not called)
immediate, name="" LISTENER imm in APPLY_REQUEST_VALUES 2 name=start
(MyFaces printed the same with INVOKE_APPLICATION(5) and APPLY_REQUEST_VALUES(2).) The ordinary listener sees the new model
value and is skipped by a validation failure; the immediate one runs first, before conversion, validation and the model update
of the executed inputs, so it sees the old value and runs even when the input is invalid. That is exactly the
immediate of the lifecycle page, with the same consequences, and the same advice: use it for a Cancel-style action, not as a
way to skip validation. For "validate only these fields", execute is the clean tool.
The Signature Differs
The VDL documentation says the method takes an AjaxBehaviorEvent. A method without parameters also worked on Mojarra
(listener="#{jsBean.noArg}" ran) and failed on MyFaces with MethodNotFoundException: Method not found:
…JsBean@74b45567.noArg(jakarta.faces.event.AjaxBehaviorEvent) in the partial response. Declare the parameter, even if
the method does not use it.
The event gives the source component (event.getComponent()) and the behaviour (event.getBehavior()); the request
parameters and the FacesContext are available in the usual way. The listener is the place to change model state and to
queue messages, and, via PartialViewContext (Ajax from Java: PartialViewContext and AjaxBehavior), to add render targets that the page did not name.
The Partial Lifecycle
The lifecycle page describes the six phases and how the partial lifecycle visits fewer components. Here is the same request
with a trace, using a phase listener that prints the PartialViewContext ids (the review preview, Mojarra 4.1.17; the MyFaces
trace has the same phases):
PHASE before RESTORE_VIEW ajax=true
PHASE after RESTORE_VIEW ajax=true exec=[review:reviewer, review:rating] render=[review:summary, review:reviewerMsg, review:ratingMsg]
PHASE before APPLY_REQUEST_VALUES ...
PHASE after APPLY_REQUEST_VALUES ...
PHASE before PROCESS_VALIDATIONS ...
PHASE after PROCESS_VALIDATIONS ...
PHASE before UPDATE_MODEL_VALUES ...
SET reviewer=Ann
SET rating=4
PHASE after UPDATE_MODEL_VALUES ...
PHASE before INVOKE_APPLICATION ...
PHASE after INVOKE_APPLICATION ...
PHASE before RENDER_RESPONSE ...
PHASE after RENDER_RESPONSE ...
-
Restore View. The view state posted in
jakarta.faces.ViewStateis used to restore the component tree, and thePartialViewContextreads the execute and render lists from the request.FacesContext.isPostback()is true. -
Apply Request Values. Only the execute ids and their descendants decode. The behaviour renderer checks
jakarta.faces.behavior.eventandjakarta.faces.sourceand queues theAjaxBehaviorEvent. (immediate="true"broadcasts it here.) -
Process Validations. The same components convert and validate. A failure calls
renderResponse()and skips phases 4 and 5. -
Update Model Values. Only the executed components write their model;
SET reviewer=AnnandSET rating=4are the setters. The fields that were not executed were not touched, which is the point of narrowingexecute. -
Invoke Application. The listener runs here, and the action of an executed command component, and so does navigation.
-
Render Response. The render ids are encoded into
updateelements. If the lifecycle callsresponseComplete()earlier (a redirect, a file download), there is no render phase.
UIViewRoot takes the decision between full and partial processing in its processDecodes, processValidators and
processUpdates and encodeChildren: they call PartialViewContext.processPartial when isPartialRequest() is true, for
rendering when isAjaxRequest() is true. A custom phase listener or system event listener sees the same phases and the same
ids; one trap is that getExecuteIds() and getRenderIds() cannot be read before the view has been restored: calling them in
beforePhase of Restore View threw a NullPointerException (root is null) in Mojarra 4.1.17.
Source: Jakarta Faces 4.1 specification, 2.2 Standard Request Processing Lifecycle Phases and Jakarta EE Tutorial: Ajax Request Lifecycle
Dependent Menus: Execute the Source, Render the Dependent
The classic Ajax recipe is a pair of menus, where the second one depends on the first. The author menu executes only itself
(default execute="@this"), so the dependent required menu is not validated while the user is still choosing, and renders
the book menu:
<h:form id="pick">
<h:selectOneMenu id="author" value="#{cascadeBean.author}">
<f:selectItem itemLabel="-- author --" itemValue="" noSelectionOption="true"/>
<f:selectItems value="#{cascadeBean.authors}"/>
<f:ajax render="book"/>
</h:selectOneMenu>
<h:selectOneMenu id="book" value="#{cascadeBean.bookId}" required="true">
<f:selectItem itemLabel="-- book --" itemValue="" noSelectionOption="true"/>
<f:selectItems value="#{cascadeBean.books}" var="b" itemValue="#{b.id}" itemLabel="#{b.title}"/>
</h:selectOneMenu>
</h:form>
package org.example.bookshelf.web;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.List;
import org.example.bookshelf.model.Book;
import org.example.bookshelf.service.BookService;
@Named
@ViewScoped
public class CascadeBean implements Serializable {
@Inject
private BookService books;
private String author;
private Long bookId;
private Integer year = 1965;
private Integer copies = 1;
public List<String> getAuthors() { return books.authors(); }
public List<Book> getBooks() {
return author == null || author.isEmpty() ? List.of() : books.byAuthor(author);
}
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public Long getBookId() { return bookId; }
public void setBookId(Long bookId) { this.bookId = bookId; }
public Integer getYear() { return year; }
public void setYear(Integer year) { this.year = year; }
public Integer getCopies() { return copies; }
public void setCopies(Integer copies) { this.copies = copies; }
}
The year and copies properties are used by the resetValues page below. Choosing William Gibson in a browser changed the book menu to -- book --, Neuromancer, Count Zero. The getBooks()
getter is called while the menu is rendered, after the model update of the author, so it sees the new author. That works
because the getter is a plain, cheap derivation; a getter that queries a database must be backed by a cached field.
resetValues
A component that took part in a request keeps state in the component tree: the local value of an input that converted and
validated successfully stays set until the model is updated, and the model is updated only if the whole request is valid.
So a second request that re-renders such an input shows the typed value, not the model’s. The resetValues attribute exists
for this case. It makes the server call UIViewRoot.resetValues(context, clientIds) on the render ids, which calls
EditableValueHolder.resetValue() on each input: the submitted value, the local value and the valid flag are cleared and the
model value is shown again.
The test page has a valid year and an invalid copies (range 1 to 10), a Check button that executes both and re-renders
them, and two Cancel buttons that execute only themselves and re-render the inputs, one with resetValues="true":
<h:form id="f">
<h:inputText id="year" value="#{cascadeBean.year}"/>
<h:inputText id="copies" value="#{cascadeBean.copies}">
<f:validateLongRange minimum="1" maximum="10"/>
</h:inputText>
<h:message id="copiesMsg" for="copies"/>
<h:commandButton id="check" value="Check">
<f:ajax execute="year copies" render="year copies copiesMsg"/>
</h:commandButton>
<h:commandButton id="plain" value="Cancel">
<f:ajax execute="@this" render="year copies copiesMsg"/>
</h:commandButton>
<h:commandButton id="clean" value="Cancel clean">
<f:ajax execute="@this" render="year copies copiesMsg" resetValues="true"/>
</h:commandButton>
</h:form>
With the model year=1965 and the browser typing 1970 and 99 and pressing Check, then each Cancel (a fresh page for the
second):
after Check: year=1970 copies=99 msg=f:copies: Validation Error: Specified attribute is not between 1 and 10.
after Cancel: year=1970 copies=1 msg=
after Cancel clean: year=1965 copies=1 msg=
The identical result came from both implementations. year was valid, so its local value 1970 stayed in the saved view
state while the model was never updated (the other input failed); the plain Cancel therefore re-rendered 1970, which looks
like a value the application accepted, and resetValues brought back the model’s 1965. The invalid copies shows 1 in
both Cancel variants because the invalid submitted value is a transient property of the component and is not kept between
requests. resetValues clears the valid flag too, so a component that failed validation no longer counts as invalid. The
same switch exists as the resetValues request option of faces.ajax.request (the request parameter
jakarta.faces.partial.resetValues=true) and as an attribute of h:commandScript. The JavaScript documentation warns not to
use keywords such as @form or @this in the render list with resetValues, because the method does not descend into the
children of the listed components. In the test, render="@form" with resetValues="true" did reset both inputs on Mojarra
4.1.17 and MyFaces 4.1.4; list the input ids and do not rely on it. The core tags page covers f:resetValues as well,
which does the same for non-Ajax submits.
delay, Queueing and disabled
The Request Queue
The specification requires every Ajax request to go through a client-side queue so that requests are sent in the order they
were initiated: the oldest waiting request is sent next; when it completes (success or error) it leaves the queue, and the
next one is sent. The page below has three buttons whose listener sleeps 400 milliseconds on the server and records which
button called it; onevent records when the client saw each stage (times are milliseconds since the first click):
<h:form id="f">
<h:commandButton id="a" value="A">
<f:attribute name="label" value="A"/>
<f:ajax listener="#{slowBean.work}" render="log" onevent="ev"/>
</h:commandButton>
<h:commandButton id="b" value="B">
<f:attribute name="label" value="B"/>
<f:ajax listener="#{slowBean.work}" render="log" onevent="ev"/>
</h:commandButton>
<h:outputText id="log" value="#{slowBean.log}"/>
</h:form>
<pre id="out"></pre>
<script>
const t0 = Date.now();
function ev(d) {
document.getElementById('out').textContent += (Date.now() - t0) + 'ms ' + d.source.id + ' ' + d.status + '\n';
}
</script>
package org.example.bookshelf.web;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.inject.Named;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@Named
@ApplicationScoped
public class SlowBean {
private final List<String> log = new CopyOnWriteArrayList<>();
public void work(AjaxBehaviorEvent event) throws InterruptedException {
String who = String.valueOf(event.getComponent().getAttributes().get("label"));
Thread.sleep(400);
log.add(who);
}
public String getLog() { return String.join(",", log); }
}
Clicking A, B and A immediately one after the other produced, on both implementations (Mojarra shown):
1ms f:a begin
422ms f:a complete
423ms f:a success
423ms f:b begin
837ms f:b complete
837ms f:b success
837ms f:a begin
1257ms f:a complete
1257ms f:a success server log: A,B,A
The begin event of a queued request fires when it leaves the queue and is sent, not when the user clicked, and the three
requests ran strictly one after the other, in order, on the server as well. The consequence is useful and dangerous: a
double click on a submit button sends two requests (the second after the first finished), so the action runs twice. The
queue guarantees order, not idempotence: make the action idempotent, hide or disable the trigger in the begin handler
(Events on the Client: onevent and addOnEvent), or use delay.
delay
delay="500" (or {delay: 500} in the JavaScript API) discards a request if another call to faces.ajax.request arrives
within that many milliseconds; only the last call is sent. This debounces keystrokes. Three clicks on a button with
delay="500" produced one request, whose begin came about 500 ms after the first click:
503ms f:c begin
916ms f:c complete
917ms f:c success (Mojarra; MyFaces: 507 / 928 / 932)
Source: Jakarta Faces 4.1 VDL: f:ajax
The literal none, or no delay, turns it off. The live search of the first example uses it so that fast typing sends one
request per pause rather than one per key.
disabled
disabled="true" (or an expression) keeps the behaviour from being rendered. It is the tag-level switch for "Ajax only in some
cases": a component whose f:ajax is disabled is rendered without the handler. A disabled f:ajax inside a group had
different effects on the two implementations; see Grouping Components.
Grouping Components
An f:ajax that is not inside a component but around others applies to every ClientBehaviorHolder inside it, each with
its own default event. The tutorial shows a group whose event and render apply to each member, and a nested f:ajax that makes the
functionality cumulative. The group below wraps four fields and a command:
<h:form id="f">
<f:ajax render="out">
<h:inputText id="a" value="#{jsBean.name}"/>
<h:inputText id="b" value="#{jsBean.name}">
<f:ajax event="keyup" render="out2"/>
</h:inputText>
<h:commandButton id="c" value="c" action="#{jsBean.go}"/>
<h:inputText id="d" value="#{jsBean.name}">
<f:ajax disabled="true"/>
</h:inputText>
<h:inputText id="e" value="#{jsBean.name}">
<f:ajax disabled="#{jsBean.flag}" render="out"/>
</h:inputText>
<h:outputText id="plain" value="no handlers"/>
</f:ajax>
<h:outputText id="out"/><h:outputText id="out2"/>
</h:form>
What each implementation rendered (event attributes only):
| Field | Mojarra 4.1.17 | MyFaces 4.1.4 |
|---|---|---|
|
|
the same |
|
|
|
|
|
the same |
|
no handler at all |
the group’s handler |
|
one |
two handlers chained: two identical requests per change (counted in the server log) |
|
no handler |
no handler |
So the group is cumulative on both for different events. A nested f:ajax for the same event was rendered once on Mojarra and
twice on MyFaces (a double request per change), and a nested disabled="true" left the field without any handler on Mojarra and
with the group’s handler on MyFaces. Use a group for the plain case (several components, one setting), and do not nest a
second f:ajax for the same event inside it.
The JavaScript API
All of the API is in faces.js, in the namespace faces. The specification (chapter 14) and the JavaScript documentation list faces.ajax.request,
faces.ajax.response, faces.ajax.addOnEvent, faces.ajax.addOnError, faces.getViewState, faces.getClientWindow,
faces.getProjectStage and faces.util.chain; the JavaScript documentation also lists the properties faces.contextpath,
faces.separatorchar, faces.specversion and faces.implversion, and the faces.push namespace for WebSocket (page on
WebSocket push).
Getting faces.js onto the Page
The script is the resource faces.js in the library jakarta.faces
(ResourceHandler.FACES_SCRIPT_RESOURCE_NAME and FACES_SCRIPT_LIBRARY_NAME). It is added automatically when the page contains
an f:ajax or an h:commandScript, so the usual pages do nothing. You must add it yourself in three
situations, with h:outputScript:
<h:head>
<title>Status</title>
<h:outputScript library="jakarta.faces" name="faces.js" target="head"/>
<h:outputScript library="js" name="bookshelf-ajax.js" target="head"/>
</h:head>
-
Your own script calls
faceswhile it loads (the example below). Without the first line, a script loaded as a resource ran beforefaces.json Mojarra 4.1.17 and on MyFaces 4.1.4: the head containedbookshelf-ajax.jsfirst andfaces.jssecond, so the script could not register its callbacks (in the test the failure message simply never appeared). Resources in the head keep the order of the tags, so listfaces.jsfirst. For an inline<script>the order is worse: Mojarra moved the automatically addedfaces.jsto the end of the head, after the inline script, so an inline script inh:headthat usesfacesat load time fails. Put such a script in a resource, or in the body. -
The page uses the API from
onclickattributes and nof:ajax, because nothing then adds it. -
A behaviour is attached from Java (Ajax from Java: PartialViewContext and AjaxBehavior). Mojarra 4.1.17 did not add
faces.jsfor anAjaxBehavioradded programmatically, so the generatedonkeyuphandler failed withmojarra is not defined; MyFaces added it. A component class can ask for it with@ResourceDependency(library = "jakarta.faces", name = "faces.js", target = "head")(the specification’s annotation approach).
The old names do not exist any more. Requesting jsf.js from jakarta.faces.resource/ and javax.faces.resource/jsf.js
returned 404 on both implementations, and window.jsf is undefined; ResourceHandler.JSF_SCRIPT_LIBRARY_NAME and
JSF_SCRIPT_RESOURCE_NAME are deprecated for removal in the API documentation.
faces.ajax.request
faces.ajax.request(source, event, options) sends the request:
-
sourceis the DOM element that triggers it, or its id. The request is sent from that element’s form (and fails if there is none or if thejakarta.faces.ViewStatefield is missing). -
eventis the DOM event (optional; its type is sent asjakarta.faces.partial.event). -
optionsis an object withexecute,render(space-delimited client ids, or the keywords),onevent,onerror(functions),params(an object of extra request parameters),delayandresetValues.
The default execute is @this, the default render is @none. The request is always a POST to the form’s action URL
(or to the hidden jakarta.faces.encodedURL field’s value, if present); the header is Faces-Request: partial/ajax; the
parameters sent are jakarta.faces.ViewState, jakarta.faces.partial.ajax=true, jakarta.faces.source and, when the view
uses the client window, jakarta.faces.ClientWindow; plus the execute and render lists, resetValues, and the extra
parameters. The JavaScript documentation adds that a form with enctype="multipart/form-data" is sent through a hidden
inline frame instead of XMLHttpRequest with the same parameters, and that the begin, complete and success events
are emulated for it (this was not run on this scratch application).
<h:form id="f">
<h:inputText id="name" value="#{jsBean.name}"/>
<h:outputText id="out" value="#{jsBean.last}"/>
<h:commandButton id="btn" value="Go"
onclick="faces.ajax.request(this, event, {execute: 'f:name', render: 'f:out', params: {tag: 'T1', other: 'two'}, resetValues: true}); return false;"/>
</h:form>
From a browser, that request carried (Mojarra):
f=f f:name=start jakarta.faces.ViewState=... jakarta.faces.partial.ajax=true
jakarta.faces.partial.execute=f:btn f:name jakarta.faces.partial.render=f:out
jakarta.faces.partial.resetValues=true jakarta.faces.source=f:btn other=two tag=T1
The return false keeps the button from also submitting the form normally. Two calls within delay milliseconds sent only
the second, with its own options, on both implementations. Ids in the options are client ids of the rendered page (f:name,
because the form is a naming container), written out in full; the keywords work too. The f:ajax tag generates calls like
this one: Mojarra writes mojarra.ab(this,event,'action','@form','f:out',{...}) and MyFaces
myfaces.ab(this,event,'action','@form','f:out',{}), thin wrappers of faces.ajax.request that are implementation
internals and must not be called by application code.
The Other Functions
| Function | What it does |
|---|---|
|
Register a callback for every Ajax request of the page (Events on the Client: onevent and addOnEvent, Ajax Error Handling). They throw if the argument is not a
function ( |
|
The encoded form data, including the hidden view state, as the string that |
|
Calls the functions in order and stops when one returns |
|
The |
|
The client window id of the form or |
|
The context path ( |
h:commandScript
h:commandScript is the component for "call the server from a script". It renders a JavaScript function with the given
name; calling the function (with an optional object of extra parameters) sends an Ajax request that behaves like a command
button’s: the action and actionListener of the component run, and execute and render apply. The attributes (VDL
documentation): name (required), autorun, action, actionListener, execute, render, resetValues, onevent,
onerror, immediate, rendered, binding and id. It is a command component; it is not among the ClientBehaviorHolder implementations that the API
documentation lists, so there is no f:ajax to nest in it, and its own execute, render and onevent attributes do that job.
<h:head><title>Command script</title></h:head>
<h:body>
<script>var bookshelf = {};</script>
<h:form id="f">
<h:commandScript name="refreshReviews" action="#{jsBean.refresh}" render="out"/>
<h:commandScript name="bookshelf.refreshStock" action="#{jsBean.refresh}" render="out"/>
<h:outputText id="out" value="#{jsBean.last}"/>
</h:form>
</h:body>
public void refresh() {
last = "refreshed:" + name + ":"
+ faces.getExternalContext().getRequestParameterMap().get("tag");
}
Calling refreshReviews({tag: 'A'}) and bookshelf.refreshStock({tag: 'B'}) from the console updated out to
refreshed:start:A and refreshed:start:B on both implementations. A name without a period is declared with var; a
namespaced name is assigned (bookshelf.refreshStock = function...), so on Mojarra the namespace object must already exist
(MyFaces rendered a myfaces.reserveNamespace('bookshelf.refreshStock') call before the assignment). The function takes
extra parameters from an object argument.
Two quirks were observed. MyFaces escaped an underscore in the name (name="bookshelf_refresh" became the function
bookshelf_5Frefresh), so calls to the name you wrote failed; use names without underscores. With autorun="true", the
function is called when the page has loaded; on Mojarra the load event object is passed as the parameter object, so the
request also carried isTrusted=true, a harmless extra parameter. autorun is a one-shot at load. Polling is better done
with setInterval calling the function, or with the WebSocket push page for server-initiated updates.
Events on the Client: onevent and addOnEvent
Two ways to observe a request: the onevent attribute of f:ajax (or the onevent option of faces.ajax.request) for one
behaviour, and faces.ajax.addOnEvent for every Ajax request of the page. Both functions receive a data object. A request
goes through three events:
| Status | When |
|---|---|
|
Immediately before the request is sent (for a queued request, when it leaves the queue). |
|
Immediately after the response arrived. For a successful response, immediately before |
|
Immediately after |
| Property | Value |
|---|---|
|
|
|
|
|
The DOM element that triggered the request. |
|
The HTTP status and the response (not present for |
The property sets observed in the browser agree: begin had the keys source, status, type; complete and success
added responseCode, responseText and responseXML.
(function () {
const busy = () => document.getElementById('busy');
const failure = () => document.getElementById('ajax-failure');
faces.ajax.addOnEvent(function (data) {
if (data.status === 'begin') {
busy().hidden = false;
failure().textContent = '';
} else if (data.status === 'complete') {
busy().hidden = true;
}
});
faces.ajax.addOnError(function (data) {
busy().hidden = true;
if ((data.errorName || '').includes('ViewExpiredException')) {
window.location.reload();
} else {
failure().textContent = 'The request failed. Please try again.';
}
});
})();
<h:head>
<title>Status</title>
<h:outputScript library="jakarta.faces" name="faces.js" target="head"/>
<h:outputScript library="js" name="bookshelf-ajax.js" target="head"/>
</h:head>
<h:body>
<h:form id="f">
<h:commandButton id="ok" value="OK" action="#{reviewBean.submit}">
<f:ajax execute="@this" render="out"/>
</h:commandButton>
<h:commandButton id="fail" value="Fail" action="#{reviewBean.failing}">
<f:ajax execute="@this" render="out"/>
</h:commandButton>
<h:outputText id="out" value="#{reviewBean.summary}"/>
</h:form>
<p id="busy" hidden="hidden">Working...</p>
<p id="ajax-failure"></p>
</h:body>
In the test the OK button left ajax-failure empty and hid the indicator at complete, and the Fail button, whose action
throws, showed The request failed. Please try again. on both implementations. The text is the application’s own; it does not
echo the server’s exception, which is what Ajax Error Handling recommends. The handlers touch the DOM with textContent and hidden,
never with innerHTML built from responseText or an error message.
The Partial Response
The response of an Ajax request is text/xml, with Cache-Control: no-cache (Mojarra) or Cache-control: no-cache plus
Pragma: no-cache and Expires: -1 (MyFaces). The root is partial-response; the specification’s schema describes it, and
faces.ajax.response documents how each element is applied. Elements are processed in the order they appear.
| Element | Effect on the client |
|---|---|
|
Replaces the contents of the element |
|
Insert markup before or after an element. |
|
Removes the element. |
|
Sets attributes of an element. |
|
Executes JavaScript. |
|
Navigates the browser to the URL. |
|
Signals a |
|
Implementation-specific. |
Three of these appeared in the runs. A redirect from an action (return "js?faces-redirect=true") was answered by both
implementations with <redirect url="/bookshelf/js.xhtml"></redirect>, no changes, and the browser’s location changed to
that URL (on Mojarra and MyFaces). render="@all" produced a single update id="jakarta.faces.ViewRoot" holding the whole
document. And PartialViewContext.getEvalScripts() produced an eval (Ajax from Java: PartialViewContext and AjaxBehavior).
Two implementation details are visible in the answers. MyFaces adds an id to the root (partial-response
id="j_id__v_0") and writes the XML declaration with double quotes, and the view state element id differs
(j_id1:jakarta.faces.ViewState:0 on Mojarra, j_id__v_0:jakarta.faces.ViewState:1 on MyFaces); the client matches by the
jakarta.faces.ViewState part. When the render list is empty (@none), the response is a lone view state update:
<?xml version='1.0' encoding='UTF-8'?>
<partial-response><changes><update id="j_id1:jakarta.faces.ViewState:0"><![CDATA[8899450177001203622:2418558211659014539]]></update></changes></partial-response>
Ajax from Java: PartialViewContext and AjaxBehavior
PartialViewContext
FacesContext.getPartialViewContext() gives the object. Its methods (API documentation): isAjaxRequest(),
isPartialRequest(), isExecuteAll(), isRenderAll(), getExecuteIds(), getRenderIds() (a mutable Collection),
getEvalScripts() (a mutable List), getPartialResponseWriter(), processPartial(PhaseId) and isResetValues().
The two mutable collections let a listener change what the response contains. The listener below saved a title, added a
panel outside the form to the render list and asked the browser to run a script:
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.PartialViewContext;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.inject.Inject;
import jakarta.inject.Named;
@Named
@RequestScoped
public class PartialBean {
@Inject
private FacesContext faces;
private String title = "Dune";
private int counter;
public void save(AjaxBehaviorEvent event) {
counter++;
PartialViewContext pvc = faces.getPartialViewContext();
pvc.getRenderIds().add("stats");
pvc.getEvalScripts().add("document.getElementById('stats').dataset.saved = 'yes';");
}
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public int getCounter() { return counter; }
}
<h:form id="f">
<h:inputText id="title" value="#{partialBean.title}"/>
<h:commandButton id="save" value="Save">
<f:ajax execute="title" render="out" listener="#{partialBean.save}"/>
</h:commandButton>
<h:outputText id="out" value="#{partialBean.title}"/>
</h:form>
<h:outputText id="stats" value="saved=#{partialBean.counter}"/>
The page asked only for out. The response contained updates for f:out and stats and an
<eval>document.getElementById('stats').dataset.saved = 'yes';</eval>; in the browser, stats read
saved=1 and had data-saved="yes" on both implementations. (The order of eval and the view state element differs: Mojarra
writes eval after the view state, MyFaces before.) The id added to the render list is a client id. Never build a script
for getEvalScripts() from user input: it is executed in the page, so a title concatenated into it is cross-site scripting.
Partial View Traversal
How does the server process only the components in the execute list and write only those in the render list? The
specification calls it partial view traversal (section 13.4) and deliberately does not prescribe a strategy: it requires
only that UIViewRoot.processDecodes, processValidators and processUpdates call
PartialViewContext.processPartial(PhaseId) when the request is a partial one, and that the encoding of an Ajax request
goes through the same method. processPartial works on the client ids in getExecuteIds() (for the three execute phases) and
getRenderIds() (for Render Response); both collections are filled from the request parameters
jakarta.faces.partial.execute and jakarta.faces.partial.render, which faces.js fills with the client ids that the
search expressions resolved to (see execute and render). The API for such a walk is UIComponent.visitTree: it takes a
VisitContext that carries the ids and a set of VisitHint values, and a callback that is called for every component the
visit reaches, which is how a partial phase can run on selected components only. The hints are: VisitHint.EXECUTE_LIFECYCLE says that the visit is part of the execution of a lifecycle phase,
SKIP_UNRENDERED visits only the rendered subtrees, SKIP_ITERATION asks components that normally visit their children
several times (the iterating ones, such as UIData) to visit them once, and SKIP_TRANSIENT visits only non-transient subtrees. The VisitContext used for
a partial request is an implementation class (PartialVisitContext in Mojarra’s com.sun.faces.component.visit and in
MyFaces' org.apache.myfaces.component.visit, not part of the API); application code obtains one from
VisitContext.createVisitContext(FacesContext, Collection<String> ids, Set<VisitHint> hints) and never names it.
The two shortcuts skip the id-based visit. @all in execute makes isExecuteAll() true and in render makes
isRenderAll() true: the first runs the normal full-tree phases and the second renders the whole view, as the update of a
single element jakarta.faces.ViewRoot in the response. @none contributes no id, so the visit has nothing to reach;
with execute="@none" not even the f:ajax listener ran on Mojarra.
The probe below replays a request’s render ids through the same API an implementation uses. The listener builds a
VisitContext from getRenderIds() and visits the view, collecting what a render of those ids would reach:
@Named
@RequestScoped
public class TraversalBean {
private String found = "";
public void inspect(AjaxBehaviorEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
PartialViewContext pvc = context.getPartialViewContext();
List<String> visited = new ArrayList<>();
if (!pvc.getRenderIds().isEmpty()) {
VisitContext visit = VisitContext.createVisitContext(
context, pvc.getRenderIds(), EnumSet.of(VisitHint.SKIP_UNRENDERED));
context.getViewRoot().visitTree(visit, (ctx, component) -> {
visited.add(component.getClientId(ctx.getFacesContext()) + ":" + component.getClass().getSimpleName());
return VisitResult.ACCEPT;
});
}
found = "executeAll=" + pvc.isExecuteAll() + " renderAll=" + pvc.isRenderAll()
+ " execute=" + pvc.getExecuteIds() + " render=" + pvc.getRenderIds() + " visited=" + visited;
}
public String getFound() {
return found;
}
}
<h:form id="f">
<h:inputText id="title" value="#{partialBean.title}"/>
<h:commandButton id="save" value="Save">
<f:ajax execute="@form" render="out panel" listener="#{traversalBean.inspect}"/>
</h:commandButton>
<h:commandButton id="all" value="All">
<f:ajax execute="@all" render="@all" listener="#{traversalBean.inspect}"/>
</h:commandButton>
<h:commandButton id="none" value="None">
<f:ajax execute="@none" render="@none" listener="#{traversalBean.inspect}"/>
</h:commandButton>
<h:outputText id="out" value="#{partialBean.title}"/>
<h:panelGroup id="panel" layout="block"><h:outputText id="found" value="#{traversalBean.found}"/></h:panelGroup>
</h:form>
Hand-made Ajax POSTs (with curl, sending the ids that faces.js would send, such as execute=f for @form)
produced the following in found:
| Button | Mojarra 4.1.17 | MyFaces 4.1.4 |
|---|---|---|
Save ( |
|
|
All ( |
|
|
None ( |
the listener did not run; the response contained only the view state |
the listener ran with |
So code that reads the id collections must not assume what is in them: after @all the collections hold the keyword on
Mojarra and nothing or the view root on MyFaces, the source component may or may not be in execute, and the visit reaches only
components that exist and are rendered (a component inside a rendered="false" parent is skipped by SKIP_UNRENDERED). Use
isExecuteAll() and isRenderAll() for the whole-view cases. Visiting the tree yourself is the right tool for tooling and
diagnostics; to change what an Ajax request renders, add ids to getRenderIds() as the previous section did.
A PartialViewContext for Every Request
To do the same for every Ajax request, decorate the factory: PartialViewContextFactory and PartialViewContextWrapper are
the extension points (registered in faces-config.xml). The wrapper below always adds the messages component to the render
list, so a page cannot forget it:
package org.example.bookshelf.web;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.PartialViewContext;
import jakarta.faces.context.PartialViewContextFactory;
import jakarta.faces.context.PartialViewContextWrapper;
import jakarta.faces.event.PhaseId;
public class MessagesPartialViewContextFactory extends PartialViewContextFactory {
public MessagesPartialViewContextFactory(PartialViewContextFactory wrapped) {
super(wrapped);
}
@Override
public PartialViewContext getPartialViewContext(FacesContext context) {
return new MessagesPartialViewContext(getWrapped().getPartialViewContext(context));
}
static class MessagesPartialViewContext extends PartialViewContextWrapper {
MessagesPartialViewContext(PartialViewContext wrapped) {
super(wrapped);
}
@Override
public void processPartial(PhaseId phaseId) {
if (phaseId == PhaseId.RENDER_RESPONSE && isAjaxRequest() && !isRenderAll()) {
getRenderIds().add("messages");
}
super.processPartial(phaseId);
}
}
}
<faces-config 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-facesconfig_4_1.xsd"
version="4.1">
<factory>
<partial-view-context-factory>org.example.bookshelf.web.MessagesPartialViewContextFactory</partial-view-context-factory>
</factory>
</faces-config>
Source: Jakarta Faces 4.1 API: PartialViewContextFactory and Jakarta Faces 4.1 API: PartialViewContextWrapper
A page whose button used render="reviewer" only still received <update id="messages"> with Review 1 saved, on both
implementations. The component with the id messages must exist in every page that uses the factory (here the id is a
client id of a component outside any form); if one lacks it, Mojarra reports a malformedXML error to the client
(What Can Be Rendered).
A Behaviour Added in Java
f:ajax is a tag handler around AjaxBehavior. Code can attach the same behaviour, using the id
AjaxBehavior.BEHAVIOR_ID and ClientBehaviorHolder.addClientBehavior. A system event listener does it when a component
enters the view, and the guard keeps a postback (when the event is published again) from adding it twice:
package org.example.bookshelf.web;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.behavior.AjaxBehavior;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.ComponentSystemEvent;
import jakarta.inject.Named;
import java.util.List;
@Named
@ApplicationScoped
public class AjaxAttacher {
public void attach(ComponentSystemEvent event) {
UIComponent component = event.getComponent();
if (component instanceof ClientBehaviorHolder holder && !holder.getClientBehaviors().containsKey("keyup")) {
FacesContext context = FacesContext.getCurrentInstance();
AjaxBehavior ajax = (AjaxBehavior) context.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);
ajax.setExecute(List.of("@this"));
ajax.setRender(List.of("echo"));
ajax.setDelay("200");
holder.addClientBehavior("keyup", ajax);
}
}
}
<h:head>
<title>Programmatic</title>
<h:outputScript library="jakarta.faces" name="faces.js" target="head"/>
</h:head>
<h:body>
<h:form id="f">
<h:inputText id="q" value="#{jsBean.name}">
<f:event type="postAddToView" listener="#{ajaxAttacher.attach}"/>
</h:inputText>
<h:outputText id="echo" value="#{jsBean.name}"/>
</h:form>
</h:body>
The input rendered onkeyup="mojarra.ab(this,event,'keyup','@this','f:echo',{'delay':'200'})" on Mojarra and
onkeyup="myfaces.ab(this,event,'keyup','f:q','f:echo',{'delay':'200'})" on MyFaces, and typing in a browser updated echo
(abc, then abcd) on both. Without the h:outputScript, Mojarra did not load faces.js and the handler failed (Getting faces.js onto the Page).
AjaxBehavior has setExecute, setRender, setOnevent, setOnerror, setDelay, setResetValues, setDisabled,
setImmediate and addAjaxBehaviorListener, one for each tag attribute. PartialResponseWriter is for the rarer case of a
component or a handler that writes its own partial response (startDocument, startUpdate(id), endUpdate(), delete(id),
redirect(url), startEval(), startError(name) and so on); the specification asks implementations to use it so that nested
CDATA sections are escaped correctly. A custom renderer needs none of this: it is rendered by the normal update machinery.
Ajax Error Handling
Errors reach the page through the error callbacks, and they come in four kinds. The tutorial and the specification list the
values of status in the data object:
| Status | Meaning |
|---|---|
|
The HTTP status is missing or outside 200 to 299. |
|
The response contains an |
|
The response does not follow the format, or an |
|
The server sent nothing. |
The data object has everything of the event payload and, in addition, description, errorName and errorMessage. The
onerror attribute of f:ajax and the onerror option name a function for one request; faces.ajax.addOnError registers one for
all. On the server side, an exception thrown in an action or listener is turned into the error element, by an
ExceptionHandler installed for Ajax requests: its error-name is the class of the throwable and error-message the cause or
message. Compare what the two implementations produced for reviewBean.failing(), which throws
IllegalStateException("The review service is down") (the HTTP status is 200 in every case):
| Case | Mojarra 4.1.17 | MyFaces 4.1.4 |
|---|---|---|
Exception in an action, |
|
|
Same, |
the message is replaced by |
the same full message as in |
Client data in the browser |
|
|
Alert without a handler, |
|
no dialog was raised |
View state from another session |
|
|
A view state that is not valid base64 ( |
|
|
The lessons, in order of importance:
-
Do not show the server’s message to the user. On MyFaces 4.1.4 the
Productionstage still put the exception text in the response, and exception text is information about the application’s internals. Show your own text (as the example above does), log on the server, and, if the container’s default is not acceptable, install an AjaxExceptionHandler(the exception handling page coversExceptionHandler,ExceptionHandlerFactoryand the OmniFacesFullAjaxExceptionHandlerthat renders an error page for Ajax requests). -
Do not match
errorNamefor equality. Mojarra prefixes the name withclass `, and MyFaces wraps the exception in a `ContextAwareELException.includes('ViewExpiredException')worked on both for an expired view; the cause of a wrapped exception is not inerrorNameon MyFaces. -
An expired view is the error you must handle. The session timed out, or the server restarted, and the user clicks. The response is an ordinary 200 with an
errorelement, so the page has to react; the error handler above reloads the page. -
Development shows an alert. The specification says that in the
Developmentstage the client signals the error with a JavaScriptalert; Mojarra did, MyFaces 4.1.4 did not. InProductionthere is no alert on either: register anonerrorhandler, or the user sees nothing. -
A successful
successevent follows a server error. In both implementations, aserverErrorwas followed by asuccessevent (the response was applied; the error was an element in it), sosuccessdoes not mean "the action succeeded". Use the error callback or a rendered message for that.
Security Notes
An Ajax request is a postback; the protections of postbacks apply and must not be bypassed for convenience.
-
The view state in
jakarta.faces.ViewStateis sent with every request, and its check on the server is what keeps a forged request from another site from being processed. Do not disable view state or CSRF protection to make an Ajax call work. TheFaces-Requestheader is no protection by itself: the API documentation definesisAjaxRequest()through that header, so a script that sets it passes, but only with a valid view state of the user’s session (a bogus one failed with an error element). -
A request executes only the components named in
execute, and the server decodes only those. The hidden effect is useful: a forged value for an input that was not executed is ignored. A forged value for an input that was executed is converted and validated like any other. -
Render only what you mean to. Everything in a
renderlist is sent to the browser. Do not render components that contain data the user must not see just because they are in the same panel. -
Scripts built for
getEvalScripts(), foroneventhandlers and forh:commandScriptparameters must not concatenate user input. The values inparamsare request parameters and arrive as strings; validate them like any request parameter. -
Output of user-supplied text through
h:outputTextis escaped in updates exactly as in full pages.escape="false"on such content is cross-site scripting in Ajax updates too. -
Business logic belongs in beans and services, not in the JavaScript that calls them: authorize in the server method.
Mojarra and MyFaces
Same on both: the request format (Faces-Request, partial.execute, partial.render, behavior.event, source, ViewState),
the partial lifecycle and phase order, the immediate behaviour, delay, the queue, resetValues, PartialViewContext and
its factory, the events (begin, complete, success) and the payload keys of an ordinary event, h:commandScript calls with
parameters, programmatic AjaxBehavior, the redirect and @all responses. Differences found in the scratch application:
-
Request body: Mojarra posts only the executed controls; MyFaces posts the whole form and the hidden
formId_SUBMIT. -
Order of the executed ids: source first on Mojarra, source last on MyFaces.
-
Raw requests (for example with
curl): Mojarra ignores the source button unless it is inpartial.execute; MyFaces invokes it anyway. The keyword@formsent in a raw request is not expanded by either of them. -
Default DOM event of checkboxes and radios:
onclickon Mojarra,onchangeon MyFaces (The event Attribute and the Default Events). -
A listener without parameters: works on Mojarra,
MethodNotFoundExceptionon MyFaces. -
A render target with
rendered="false": Mojarra answersmalformedXMLand stops applying updates; MyFaces skips it. -
Nesting
f:ajaxin a group: one handler versus two for the same event, and the effect of a nesteddisabled(Grouping Components). -
An
AjaxBehavioradded in Java:faces.jsis added by MyFaces and not by Mojarra (Getting faces.js onto the Page). -
h:commandScript: underscore escaping in the name on MyFaces;isTrusted=truewithautorunon Mojarra; namespace reservation on MyFaces. -
Errors: wrapped exception names, the
Productionmessage, thestatusof a server error, the development alert, and the error for an invalid view state (Ajax Error Handling). -
Response shape: the
idonpartial-response(MyFaces), the position ofeval, the view state element id and value format. -
getExecuteIds()before the view is restored: aNullPointerExceptionon Mojarra 4.1.17 (the MyFaces behaviour was not run). -
The
specversionandimplversionvalues offaces.jsare implementation-defined (Mojarra40100and17, MyFaces400000and0in the test); do not branch on them.
What Changed
| Version | Change |
|---|---|
Faces 1.x |
No Ajax. Applications used hand-written |
Faces 2.0 |
|
Faces 2.2 |
The |
Faces 2.3 |
|
Faces 3.0 |
The namespace changed from |
Faces 4.0 |
The JavaScript resource and namespace were renamed: |
Faces 4.1 |
No Ajax-specific change in the change list. Full state saving is deprecated (it affects what a view state is, not the Ajax protocol). |
Faces 5.0 |
Under development (the latest milestone at the time of writing is |
Where the Books Are Outdated
The books of the section’s bibliography that cover Ajax are of two generations. Burns and Schalk, and Leonard, describe Faces 2.0, where Ajax first became part of the specification; Jacobi and Fallows predate it.
-
Hand-written Ajax. Jacobi and Fallows build Ajax into components with their own
XMLHttpRequestcode, script resources and response formats, and Leonard shows ajax4jsf and Dynamic Faces. All of it is replaced byf:ajax, the standard queue, the standard partial response andResourceHandler(Faces 2.0). The lasting idea is the one they teach: components that update parts of a page by asking the server for markup. The code to write today is none. -
Old names. Burns and Schalk show the script
jsf.jsin the libraryjavax.facesand the request parametersjavax.faces.partial.*, and Leonard callsjsf.ajax.request. Today they arefaces.js,faces.ajax.request, the libraryjakarta.facesandjakarta.faces.partial.*. Copying such examples fails twice: the script is missing (404 forjsf.json both implementations) and the names no longer exist. -
Newer features are missing. The books are older than
delayandresetValues(2.2),h:commandScript,PartialViewContext.getEvalScripts(), search expressions (@next,@parent,@id(...)) andf:websocket(2.3), so none of them appears; delay, Queueing and disabled, resetValues, h:commandScript and Ajax from Java: PartialViewContext and AjaxBehavior cover them. -
Libraries. ajax4jsf, Dynamic Faces and RichFaces have ended. PrimeFaces
p:ajaxand OmniFaces cover what they added; the pages on PrimeFaces Ajax and on OmniFaces describe them, and theRequestContextof the PrimeFaces books isPrimeFaces.current()today.
Common Mistakes
-
The message never appears. The
h:messagesorh:messageis not inrender. Add it, or use aPartialViewContextdecoration for the whole application (A PartialViewContext for Every Request). -
The model does not change. The input is not in
execute.@thisis the default and it executes only the trigger. -
The action works in
curland not in the browser, or the reverse. A raw request must contain the source inpartial.execute; copy the request from the browser’s network tab or from a request filter. -
The target does not update. The element is absent from the page (
rendered="false", a row that does not exist). Render a wrapper that is always present. -
A script in the head says
faces is not defined. Listfaces.jsfirst withh:outputScript, put the script in a resource after it, or in the body. -
A value reappears after a failed validation. The local value of a valid sibling stayed in the component tree; use
resetValues="true"and list the inputs. -
The listener should run for invalid input and does not. Validation failure skips Invoke Application. Do not use
immediateto get around it; read the lifecycle page. -
A double click runs the action twice. The queue serialises requests, it does not drop them. Make the action idempotent, or hide the trigger while the request is pending.
-
A listener works on one implementation only. A listener without the
AjaxBehaviorEventparameter fails on MyFaces. -
A nested
f:ajaxsends two requests. A secondf:ajaxfor the same event inside a group stacks on MyFaces 4.1.4. -
escape="false"on rendered user text. It writes the text into the DOM unescaped. -
Error handling shows the server’s text. Show a fixed message; log the rest. MyFaces 4.1.4 puts the full exception text in the response even in
Production. -
The page expires and nothing happens. Handle
ViewExpiredExceptioninaddOnErrorand reload. -
f:ajaxon a non-existing event name. The 500 appears when the view is built. Names arekeyup,change,click,action,valueChange; notonkeyup.