Search Expressions
|
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) with Eclipse Mojarra
4.1.17 and Apache MyFaces Core 4.1.4, Jakarta Servlet 6.1 and Java 17 or later (the examples were compiled with
--release 21), and, for the section on PrimeFaces, PrimeFaces 16.0.0. Search expressions were added in Faces 2.3 (the
package is jakarta.faces.component.search since the move from javax to jakarta), and the specification’s change log mentions the facility only for 2.3 (and, indirectly, an Ajax change for
composites in 4.0), so everything below also applies to Faces 4.0, but only 4.1 was run. Every view and class in this page was
deployed as an exploded Bookshelf WAR on Tomcat 11.0.26 with Weld 6.0.4.Final, once with Mojarra and once with MyFaces
(a third pair of runs added the PrimeFaces 16.0.0 jar). The Ajax behaviour was observed by loading each page in jsdom
30.1.2 with the faces.js that each implementation ships, clicking the button and reading the request that script sent and
the partial response it received; the results of the SearchExpressionHandler calls were read from a Faces view that
calls the handler. All of it was observed on 2026-10-08. Whatever was not run is marked as such.
When a page says render="results" or execute="@form" it uses a search expression: a string that names a
component relative to the component that carries the attribute. Faces 2.3 turned what used to be a pile of per-library
conventions into one specified facility with an extension point, and every standard attribute that points at another
component goes through it. This page explains the grammar, the keywords, where in the request processing lifecycle an
expression is resolved (which decides what it can see), what the two implementations do with the cases the specification
leaves open, and how to add keywords and decorate the engine.
What a Search Expression Is
The API documentation of SearchExpressionHandler defines it: a search expression is either an identifier (matched exactly
against the id property of a component), a keyword such as @this or @form, or a series of such identifiers and
keywords linked by the naming container separator character, which is the colon unless jakarta.faces.SEPARATOR_CHAR
says otherwise. A leading separator makes the expression absolute: the search starts at the view root. Without it the
search starts at the source, the component that carries the attribute. Several expressions in one attribute are separated
by spaces (and, on MyFaces, by commas; see execute and render in f:ajax).
The places that accept search expressions in Faces 4.1:
| Where | Attribute | Notes |
|---|---|---|
|
|
Documented in the VDL as "search expressions"; defaults |
|
|
Same renderer path as |
|
|
The VDL still calls it a "client identifier"; both implementations call the handler (single result, no exception when nothing matches). See Labels and Messages. |
|
|
Same as the label. See Labels and Messages. |
|
the argument |
The older method: identifiers and the separator only, no keywords. See The Resolution Algorithm. |
|
any |
The programmatic API; see The SearchExpressionHandler API. |
PrimeFaces components |
|
Through the same handler plus PrimeFaces' own keywords; see PrimeFaces Search Expressions. |
Ids, Client Ids and Naming Containers
Two names are in play. The component id is the id you write; it has to be unique only inside the closest
naming container (h:form, h:dataTable, ui:repeat, a composite component, f:subview). The client id is what
the browser sees: the ids of the enclosing naming containers and the component id joined with the separator, plus the row
index inside an iterating component (search:title, shelf:books:1:status). The pages on the architecture and on the
HTML components work through how client ids are built; what matters here is how the two names are used:
-
A search expression written with component ids is relative to the naming container of the source. Inside
<h:form id="search">the expressiontitlemeans the componentsearch:title. -
An expression that starts with the separator is absolute and is written with ids from the root:
:search:titleis the same component from anywhere in the view, and a component outside every form is:results. -
The client ids that come out are what travel in the Ajax request. Search expressions exist so that nobody has to concatenate them by hand.
-
The separator is configurable. With the context parameter
jakarta.faces.SEPARATOR_CHARset to_the same views renderedsearch_authorand a relative id and an absolute expression starting with_(_search_author) resolved to it on both implementations. Code must callUINamingContainer.getSeparatorChar(FacesContext)rather than hard-code the colon.
The specification calls the separator out in 3.2.2 (NamingContainer) and defines findComponent in 3.1.8; the latter is the
ancestor of the whole facility.
The Resolution Algorithm
The handler splits an expression at the separator into commands and resolves them from left to right. The API documentation states the algorithm:
-
If the expression starts with the separator, the base is the view root and the separator is dropped; otherwise the base is the source component.
-
The first command is resolved starting at the base. A command that starts with
@is a keyword and is given to the keyword resolvers. Any other command is an id. -
Every further command is resolved starting at the component the previous command produced.
-
A leaf keyword (
@alland@noneamong the built-in ones) cannot be followed by anything. Both implementations throw aFacesExceptionfor@none:@formand@all:@form, andisValidExpressionreturnsfalsefor them.
For an id the rules of UIComponent.findComponent apply: the lookup starts at the closest naming container of the base (the
base itself when it is one), and in an expression of several identifiers every identifier before the last must name a naming
container. filters:isbn, with filters a plain h:panelGroup, throws an IllegalArgumentException on both
implementations.
What the specification leaves open is what happens when findComponent finds nothing, and the two implementations differ.
Mojarra falls back to a client-id lookup from the view root, so an id such as results (a component that is not in the
form) or a full client id such as catalogue:title still resolves. MyFaces walks up through the enclosing naming
containers. Both therefore accept results from inside a form, which is why render="results" works in practice even
though the component lives outside the form; but they disagree about absolute client ids that contain a row index
(Inside Tables and ui:repeat). Both are observable with the probe view of the section The SearchExpressionHandler API; the table was produced from a button in the form
probe while the form catalogue held an input title:
| Expression | Result (Mojarra and MyFaces alike unless noted) | Component |
|---|---|---|
|
client id |
an input in the same form |
|
|
the first child of the form |
|
|
the siblings of the button |
|
|
the input is in the other form |
|
client id |
the other form is a naming container |
|
client id |
searches the whole view |
|
|
nothing matches; |
The Keywords
The API documentation of SearchKeywordResolver lists the twelve keywords every implementation must support:
| Keyword | Meaning (from the API documentation, with what was observed) |
|---|---|
|
The base component: the component that carries the attribute (for a behavior, the component the behavior is attached to).
In |
|
The closest ancestor form. |
|
All components in the view. A leaf. In |
|
No component. A leaf. It is the default of |
|
The parent of the base component. |
|
The closest ancestor naming container. |
|
The composite component the base belongs to. Outside a composite both implementations throw a |
|
The component after or before the base among the children of the parent. They are siblings of the base, whatever type
they are: a neighbouring |
|
The |
|
The components with that id (an id, not a client id), found by visiting the tree below the base. See Chaining and the @id Keyword. |
|
The |
The Jakarta EE Tutorial, as of this writing, lists only four keywords for f:ajax (@all, @form, @none, @this);
the specification and the API documentation have the twelve above, and the VDL for f:ajax points to
SearchKeywordResolver for "the complete list of keywords".
The view below uses a number of them in one Bookshelf catalogue search form. The bean is the small one every example of the section uses; the scenario is the same as in the page on navigation.
package org.example.bookshelf.web;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.example.bookshelf.model.Book;
/** The catalogue search of the Bookshelf example. */
@Named
@ViewScoped
public class CatalogueBean implements Serializable {
private static final List<Book> ALL = List.of(
new Book(1, "Dune", "Frank Herbert", "9780441172719"),
new Book(2, "Emma", "Jane Austen", "9780141439587"),
new Book(3, "Ubik", "Philip K. Dick", "9780575079939"));
private String title = "";
private String author = "";
private String isbn = "";
private String year = "";
private List<Book> results = new ArrayList<>(ALL);
private final Set<Long> borrowed = new HashSet<>();
private int searches;
public void search() {
searches++;
String t = title == null ? "" : title.trim().toLowerCase();
results = new ArrayList<>(ALL.stream().filter(b -> b.title().toLowerCase().contains(t)).toList());
}
public void borrow(Book book) {
borrowed.add(book.id());
}
public String status(Book book) {
return borrowed.contains(book.id()) ? "borrowed" : "available";
}
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public String getIsbn() { return isbn; }
public void setIsbn(String isbn) { this.isbn = isbn; }
public String getYear() { return year; }
public void setYear(String year) { this.year = year; }
public List<Book> getResults() { return results; }
public List<String> getFilterIds() { return List.of("title", "author"); }
public int getSearches() { return searches; }
}
<!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>Bookshelf search</title></h:head>
<h:body>
<h:form id="search">
<h:inputText id="title" value="#{catalogueBean.title}"/>
<h:panelGroup id="filters" layout="block">
<h:inputText id="author" value="#{catalogueBean.author}"/>
<h:commandButton id="refine" value="Refine" action="#{catalogueBean.search}">
<f:ajax execute="@this @parent" render="@next"/>
</h:commandButton>
<h:outputText id="hint" value="#{catalogueBean.searches} searches"/>
</h:panelGroup>
<h:commandButton id="byId" value="By id" action="#{catalogueBean.search}">
<f:ajax execute="title author" render=":results"/>
</h:commandButton>
<h:commandButton id="fromBean" value="From a bean" action="#{catalogueBean.search}">
<f:ajax execute="#{catalogueBean.filterIds}" render="results"/>
</h:commandButton>
<h:commandButton id="relative" value="Relative" action="#{catalogueBean.search}">
<f:ajax execute="@this" render="results"/>
</h:commandButton>
<h:commandButton id="wholeForm" value="Whole form" action="#{catalogueBean.search}">
<f:ajax execute="@form" render="@form results"/>
</h:commandButton>
<h:commandButton id="chain" value="Chain" action="#{catalogueBean.search}">
<f:ajax execute="@form:@child(0)" render="@root:@id(results)"/>
</h:commandButton>
<h:commandButton id="everything" value="Everything" action="#{catalogueBean.search}">
<f:ajax execute="@all" render="@all"/>
</h:commandButton>
</h:form>
<h:panelGroup id="results" layout="block">
<ui:repeat value="#{catalogueBean.results}" var="book">
<div>#{book.title}</div>
</ui:repeat>
</h:panelGroup>
</h:body>
</html>
Source: Jakarta Faces 4.1 API: SearchKeywordResolver and Jakarta EE Tutorial: Execute and Render Keywords
Clicking each button with the real faces.js produced these requests. The columns are the values of
jakarta.faces.partial.execute and jakarta.faces.partial.render; the partial response updated the elements listed
last. The source button is always added to execute:
| Button | execute sent (Mojarra) |
render sent |
Elements updated |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MyFaces sent the same sets of ids, with the source last (search:title search:author search:byId) where Mojarra puts it
first. Neither order matters to the lifecycle.
Chaining and the @id Keyword
Chaining makes the keywords composable: @form:@child(0) is the first child of the form, @parent:isbn is the sibling
named isbn reached through the parent, @composite:label is the component label inside the composite. Each step
starts where the previous one ended. Five rules and pitfalls decide whether a chain works:
-
@id(x)starts at the base and looks below it. From a button it finds nothing, because a button has no children. The intuitiverender="@id(results)"therefore fails: on Mojarra the unresolved token is sent to the browser as it is and the server ignores it silently (execute and render stayed@id(author)and nothing was processed); on MyFaces the page that contains thef:ajaxfails to render with aComponentNotFoundException(HTTP 500). Anchor it:@form:@id(author)or@root:@id(results)worked on both implementations (the second is in the view above). -
The
@idkeyword delivers every match unless told to stop.@form:@id(note)in a test view with a repeated row returned the client ids of all three rows when called programmatically.f:ajaxasks for a single component, and both implementations then used only the first row (search:rows:0:note). -
MyFaces 4.1.4 recognises
@id(x)only when the id has three or more characters. The source ofIdSearchKeywordResolvertests that the command is longer than six characters (id()counts), so@id(box)works and@id(b)does not; I observed@root:@id(b)failing and@root:@id(box)resolving on MyFaces while both worked on Mojarra. -
@rootis for chains. Asexecuteit made the server recurse until aStackOverflowErroron both implementations; asrenderMojarra answered withIllegalStateException: CDATA tags may not nestand MyFaces with an error response. Write@allinstead of a lone@root. -
Leaf keywords end the chain. Called on the handler,
@all:@formand@none:@formthrow aFacesException(message "It’s not valid to place a keyword or id after a leaf keyword" on Mojarra, "Expression cannot have keywords or ids at the right side" on MyFaces). Inf:ajax, which resolves withRESOLVE_CLIENT_SIDE, the expression is treated as a pass-through keyword expression and sent on unresolved, and the request then rendered nothing: the mistake is silent on both.
When an Expression Is Resolved
This is the part that decides what an expression can express. For f:ajax the expressions are resolved while the page is
rendered, not while the Ajax request is processed:
-
During Render Response of the page that contains the
f:ajax(the initial GET, a postback, or a partial response that re-renders the component), the renderer of the behavior calls the handler for every token ofexecuteandrender. Mojarra asksresolveClientIdonce per token; MyFaces asksresolveClientIdsonce per attribute. The handler returns client ids, the renderer writes them into the JavaScript call (myfaces.ab(this,event,'action','search:title search:author','results',{})), and keywords that the browser can handle itself (@all,@none,@form, and, on Mojarra only,@this) are written as they are. -
The Ajax request therefore carries client ids. During Apply Request Values and the later phases the
PartialViewContexthands those ids to the lifecycle (getExecuteIds(),getRenderIds()); the handler is not asked again. APhaseListenerloggedexecuteIds=[search:byId, search:title, search:author]andrenderIds=[results]for thebyIdbutton, and a wrapper like the one in Decorating the Handler that logged every call to the handler logged them all before Apply Request Values and none between there and the end of the request. -
h:outputLabelandh:messageresolve theirforwhile they are rendered, in Render Response, in the same request that made the validation messages. -
A handler call from your own code resolves at the moment you make it (The SearchExpressionHandler API).
Four consequences follow, and every one of them was observed:
-
The tree that matters is the tree of the page that rendered the script. A component that is not in the tree at that moment cannot be resolved by keyword.
-
State of the postback is invisible to
f:ajaxexpressions. A keyword such as@invalid(Custom Keywords) works inh:message for, because that attribute is resolved after validation, but inrender=it would be resolved before the user has typed anything. -
Resolution happens once per rendering of the script, not once per click. A component that a partial response re-renders gets its script resolved again: when the
refreshbutton of the rows view below re-rendered the list, thestatusexpressions of the three borrow buttons were resolved again while the response was rendered (Mojarra log). -
In a
PhaseListeneror a bean,PartialViewContext.getRenderIds()is a list of client ids, never an expression. Use the handler if you want to resolve one.
Where this sits in the lifecycle is the Render Response phase described in the page on the request processing
lifecycle (see Render Response and
the partial lifecycle); execution of the ids is the job of the other
phases of the partial lifecycle.
execute and render in f:ajax
The attributes are the main user of the facility; the page on Ajax covers the tag, the events and the partial lifecycle, and this section covers only the expressions in it.
-
executedefaults to@thisandrenderto@none. The tags the keywords select take part in Apply Request Values, Process Validations, Update Model Values and Invoke Application of the Ajax request (execute) or are encoded into the partial response (render). -
The value is either a literal of expressions separated by white space, or an EL expression that evaluates to a
Collection<String>(thefromBeanbutton above). The tokens of a literal are trimmed; extra spaces are harmless (author isbnand ` +@this + ` gave the same ids). -
Separate expressions with spaces.
f:ajax execute="author,isbn"was split by MyFaces (search:author search:isbn) but Mojarra sent the single tokenauthor,isbn, which matched nothing, so neither component was processed and no error was reported. The API documentation says that comma and space are the separator characters of the handler (getExpressionSeperatorChars, spelled as in the API), and a literal thatf:ajaxhands to Mojarra’s handler one token at a time never reaches that splitting. -
Unresolvable expressions fail loudly on MyFaces and silently on Mojarra. For
render="nosuch"MyFaces throwsComponentNotFoundException: Cannot find component for expression "nosuch" referenced from "search:go"while rendering the page (HTTP 500). Mojarra catches the exception, sends the token on as it is, and the browser later finds no element with that id. In development you want to know; the wrapper in Decorating the Handler logs each case on both. -
faces.jsadds the source component toexecute(the requests above showsearch:gonext to the ids) unless the value is@noneor@all: withexecute="@none"the request carried nojakarta.faces.partial.executeat all.
| Button | Mojarra | MyFaces |
|---|---|---|
|
|
|
|
|
|
|
|
|
The rendered form was identical in everything else.
Inside Tables and ui:repeat
An iterating component is a naming container whose children exist once per row. A button inside a row is resolved per row: the script for each row is written while the row is current, and every relative expression lands on the same row. That is the single most useful behaviour of the facility.
<!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>Bookshelf rows</title></h:head>
<h:body>
<h:form id="shelf">
<h:panelGroup id="list" layout="block">
<ui:repeat id="books" value="#{catalogueBean.results}" var="book">
<h:panelGroup id="row" layout="block">
<h:outputText id="caption" value="#{book.title}"/>
<h:commandButton id="borrow" value="Borrow" action="#{catalogueBean.borrow(book)}">
<f:ajax render="status"/>
</h:commandButton>
<h:outputText id="status" value="#{catalogueBean.status(book)}"/>
</h:panelGroup>
</ui:repeat>
</h:panelGroup>
<h:commandButton id="refresh" value="Refresh" action="#{catalogueBean.search}">
<f:ajax render="list"/>
</h:commandButton>
</h:form>
</h:body>
</html>
For the button of the second row the scripts were (from shelf:books:1:borrow, identical on both implementations):
render="status" sent shelf:books:1:status and the response updated only that element; the refresh button, which
is outside the repeat, sent shelf:list. A test view with the same structure produced these results (the row button is
inside ui:repeat id="rows" in a form search; second row; "item" is a wrapper h:panelGroup, "caption" an output
in the same row):
render= |
Mojarra | MyFaces |
|---|---|---|
|
the second row’s client id, e.g. |
same |
|
|
same |
|
|
|
|
|
|
|
|
same |
Four lessons:
-
Prefer relative ids and
@parentinside a row. They are resolved per row on both implementations and need no knowledge of the row index. -
Avoid absolute ids with a row index.
:search:rows:2:captionworks on Mojarra and fails the rendering of the whole page on MyFaces. -
Do not render a
ui:repeat(or the iterating component) by its id.ui:repeatwrites no element, so there is nothing to update, in either implementation: when therefreshbutton rendered the repeat itself (render="books") the response carried an update forshelf:books, MyFaces reported that the element was not found, and Mojarra’s script reported an error tofaces.ajax.addOnError. Wrap it in a component with an id, aslistabove, and render that.@namingcontainerand the repeat’s id from inside a row additionally resolved tosearch:rows:1on Mojarra, a client id that is a row, not an element. -
SKIP_VIRTUAL_COMPONENTSand@idshow the iteration. Without the hint@form:@id(note)returnedsearch:rows:0:note,search:rows:1:noteandsearch:rows:2:note; withSKIP_VIRTUAL_COMPONENTSit returned the single component with the client idsearch:rows:note, the one that exists in the tree without a row index (The SearchExpressionHandler API).
Inside Composite Components
A composite component is a naming container: the id of the tag is a prefix for everything in its implementation, and
that is what makes ids inside the implementation relative to the composite.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:attribute name="value"/>
</cc:interface>
<cc:implementation>
<h:panelGroup id="box" layout="block">
<h:inputText id="rating" value="#{cc.attrs.value}"/>
<h:commandButton id="rate" value="Rate" action="#{catalogueBean.search}">
<f:ajax execute="@this rating" render="label"/>
</h:commandButton>
<h:outputText id="label" value="rated #{cc.attrs.value}"/>
</h:panelGroup>
</cc:implementation>
</html>
Source: Jakarta EE Tutorial: Facelets
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:ez="jakarta.faces.composite/ez">
<h:head><title>Bookshelf stars</title></h:head>
<h:body>
<h:form id="detail">
<ez:stars id="stars" value="#{catalogueBean.year}"/>
<h:commandButton id="outside" value="Outside" action="#{catalogueBean.search}">
<f:ajax render="stars:label"/>
</h:commandButton>
</h:form>
</h:body>
</html>
Source: Jakarta EE Tutorial: Facelets and VDL: f:ajax
Inside the implementation, rating and label resolved to detail:stars:rating and detail:stars:label; from outside,
stars:label (a path through the composite) and :detail:stars:label resolved to the same component on both
implementations. Some other results with the same views (the VDL puts it more generally: inside a composite, @this in
f:ajax is remapped to the composite’s behavior targets, and a relative value is reinterpreted relative to the
declaration of f:ajax; Faces 4.0 improved f:ajax in composites, issue 1567 in the change log):
-
@compositefrom inside resolved to the composite, client iddetail:stars, and@composite:labeland@composite:boxresolved to the inner components, on both implementations. -
render="@composite"orrender="stars"(outside) fail to update anything: the composite itself writes no element with its own client id; the markup starts at the first component of the implementation (detail:stars:box). MyFaces reported that the elementdetail:starswas not found and Mojarra’s script reported an error tofaces.ajax.addOnError. Give the implementation a wrapper with an id, asboxabove, and render that. -
@namingcontainerfrom the button inside the composite resolved to the composite for the same reason, with the same non-update. -
@compositeevaluated outside a composite threw aNullPointerException(on both), so the page failed to render.
The attributes and the retargeting rules of cc:clientBehavior belong to the page on composite components.
Labels and Messages
h:outputLabel and h:message take a search expression in for (the VDL text predates the facility and calls it a client
id): both implementations call the handler with the hints RESOLVE_SINGLE_COMPONENT and IGNORE_NO_RESULT, so an
expression that matches nothing renders no for or no message instead of failing. The view below uses @next so
the labels do not need ids, and the custom keyword @invalid of the section Custom Keywords to show the first
message of the form wherever it comes from.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head><title>Bookshelf review</title></h:head>
<h:body>
<h:form id="review">
<h:outputLabel for="@next" value="Reviewer"/>
<h:inputText id="reviewer" value="#{reviewBean.reviewer}" required="true"/>
<h:outputLabel for="@next" value="Rating (1 to 5)"/>
<h:inputText id="rating" value="#{reviewBean.rating}">
<f:validateLongRange minimum="1" maximum="5"/>
</h:inputText>
<h:message id="firstError" for="@invalid"/>
<h:commandButton id="send" value="Send" action="#{reviewBean.send}"/>
</h:form>
</h:body>
</html>
Source: VDL: h:outputLabel and VDL: h:message
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
/** The review form of the Bookshelf example. */
@Named
@RequestScoped
public class ReviewBean {
private String reviewer;
private Integer rating;
public void send() {
// a real application would store the review
}
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; }
}
Both labels rendered for="review:reviewer" and for="review:rating". Posting an empty reviewer and the rating 9 made the
message review:reviewer: Validation Error: Value is required. appear (the first invalid input in tree order); posting a
reviewer and the rating 9 gave review:rating: Validation Error: Specified attribute is not between the expected values of 1
and 5.; with valid values Mojarra rendered an empty span and MyFaces rendered nothing and logged
SEVERE: Could not render Message. Unable to find component '@invalid' (calling findComponent on component 'review:firstError'). ...
on every such render. That message is MyFaces' way to say "the for resolved to nothing"; it is noise for a keyword that is
legitimately empty most of the time. See the page on messages and internationalisation for the message components.
The SearchExpressionHandler API
Application.getSearchExpressionHandler() returns the singleton handler. The package jakarta.faces.component.search has
these types:
| Type | Role |
|---|---|
|
The engine. Methods: |
|
Base class for decorators; see Decorating the Handler. |
|
The call state: the source component, the |
|
|
|
One keyword; see Custom Keywords. |
|
What a keyword resolver receives; see Custom Keywords. |
|
The |
The hints, as documented and as observed:
| Hint | Documented effect | Observed |
|---|---|---|
|
an expression that resolves to nothing returns |
|
|
only one component is resolved (lets a keyword stop a tree visit early) |
|
|
a keyword that the client can resolve is returned as it is |
|
|
only real components, not the per-row copies of iterating components |
|
resolveClientId(s) and resolveComponent(s) add RESOLVE_SINGLE_COMPONENT themselves for the single-result forms. The
standard components use these hints: f:ajax RESOLVE_CLIENT_SIDE and RESOLVE_SINGLE_COMPONENT; labels and messages IGNORE_NO_RESULT
and RESOLVE_SINGLE_COMPONENT (the hints were logged by a wrapper in the runs).
The view and the bean below call the handler from a button and list what it returns. They are the probe that produced the tables of this page.
<!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>Expression probe</title></h:head>
<h:body>
<h:form id="probe">
<h:inputText id="expression" value="#{expressionProbe.expression}"/>
<h:selectBooleanCheckbox id="ignoreNoResult" value="#{expressionProbe.ignoreNoResult}"/>
<h:selectBooleanCheckbox id="clientSide" value="#{expressionProbe.clientSide}"/>
<h:selectBooleanCheckbox id="skipVirtual" value="#{expressionProbe.skipVirtual}"/>
<h:selectBooleanCheckbox id="single" value="#{expressionProbe.single}"/>
<h:commandButton id="resolve" value="Resolve" actionListener="#{expressionProbe.resolve}">
<f:ajax execute="@form" render="lines"/>
</h:commandButton>
<h:panelGroup id="lines" layout="block">
<ui:repeat value="#{expressionProbe.lines}" var="line"><div>#{line}</div></ui:repeat>
</h:panelGroup>
</h:form>
<h:form id="catalogue">
<h:inputText id="title" value="#{catalogueBean.title}"/>
<h:inputText id="author" value="#{catalogueBean.author}"/>
</h:form>
</h:body>
</html>
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.search.SearchExpressionContext;
import jakarta.faces.component.search.SearchExpressionHandler;
import jakarta.faces.component.search.SearchExpressionHint;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.ActionEvent;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
/** Resolves a search expression from the clicked button, with the programmatic API. */
@Named
@RequestScoped
public class ExpressionProbe {
@Inject
FacesContext context;
private String expression = "";
private boolean ignoreNoResult;
private boolean clientSide;
private boolean skipVirtual;
private boolean single;
private final List<String> lines = new ArrayList<>();
public void resolve(ActionEvent event) {
UIComponent source = event.getComponent();
SearchExpressionHandler handler = context.getApplication().getSearchExpressionHandler();
Set<SearchExpressionHint> hints = EnumSet.noneOf(SearchExpressionHint.class);
if (ignoreNoResult) hints.add(SearchExpressionHint.IGNORE_NO_RESULT);
if (clientSide) hints.add(SearchExpressionHint.RESOLVE_CLIENT_SIDE);
if (skipVirtual) hints.add(SearchExpressionHint.SKIP_VIRTUAL_COMPONENTS);
if (single) hints.add(SearchExpressionHint.RESOLVE_SINGLE_COMPONENT);
SearchExpressionContext sec = SearchExpressionContext.createSearchExpressionContext(context, source, hints, null);
try {
lines.add("valid=" + handler.isValidExpression(sec, expression)
+ " passthrough=" + handler.isPassthroughExpression(sec, expression));
} catch (RuntimeException e) {
lines.add("valid/passthrough threw " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
try {
lines.add("clientIds=" + handler.resolveClientIds(sec, expression));
} catch (RuntimeException e) {
lines.add("clientIds threw " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
try {
List<String> found = new ArrayList<>();
handler.resolveComponents(sec, expression, (ctx, c) -> found.add(c.getClass().getSimpleName() + "#" + c.getId()));
lines.add("components=" + found);
} catch (RuntimeException e) {
lines.add("components threw " + e.getClass().getSimpleName() + ": " + e.getMessage());
}
System.out.println("PROBE " + expression + " hints=" + hints + " -> " + lines);
}
public String getExpression() { return expression; }
public void setExpression(String expression) { this.expression = expression; }
public boolean isIgnoreNoResult() { return ignoreNoResult; }
public void setIgnoreNoResult(boolean v) { this.ignoreNoResult = v; }
public boolean isClientSide() { return clientSide; }
public void setClientSide(boolean v) { this.clientSide = v; }
public boolean isSkipVirtual() { return skipVirtual; }
public void setSkipVirtual(boolean v) { this.skipVirtual = v; }
public boolean isSingle() { return single; }
public void setSingle(boolean v) { this.single = v; }
public List<String> getLines() { return lines; }
}
Source: Jakarta Faces 4.1 API: package jakarta.faces.component.search and Jakarta Faces 4.1 API: Application
The exceptions to expect from a call: ComponentNotFoundException when nothing matched and IGNORE_NO_RESULT is off (an
unknown keyword such as @bogus gave this one, although isValidExpression was false); a plain FacesException for a
keyword after a leaf keyword and for @child(n) with too few children; IllegalArgumentException from findComponent for an
intermediate id that is not a naming container; NullPointerException for @composite outside a composite. Check
isValidExpression first if the expression comes from configuration.
Custom Keywords
A keyword is a SearchKeywordResolver: isResolverForKeyword says whether the instance is responsible, resolve calls
SearchKeywordContext.invokeContextCallback for each component it finds and setKeywordResolved(true), and isLeaf and
isPassthrough (default false) describe the keyword. Two facts decide whether a resolver works:
-
The keyword string does not contain the
@. The handler strips the prefix before it asks the resolvers, so@invalidarrives asinvalidand@child(0)aschild(0). A resolver that compares with"@invalid"matches nothing, on both implementations;isValidExpressionthen reportsfalse. The API documentation does not say this; the built-in resolvers (for example"this".equals(keyword)in Mojarra) show it, and the failure was observed. -
What the keyword can see. It runs when the expression is resolved (When an Expression Is Resolved): at page render for
f:ajax, so it must depend on the tree, not on the request. Inh:message forit runs after validation.
package org.example.bookshelf.search;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIInput;
import jakarta.faces.component.search.SearchExpressionContext;
import jakarta.faces.component.search.SearchKeywordContext;
import jakarta.faces.component.search.SearchKeywordResolver;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;
/** Resolves {@code @invalid} to every input of the view whose validation failed in this request. */
public class InvalidKeywordResolver extends SearchKeywordResolver {
@Override
public boolean isResolverForKeyword(SearchExpressionContext context, String keyword) {
return "invalid".equals(keyword);
}
@Override
public void resolve(SearchKeywordContext context, UIComponent current, String keyword) {
FacesContext facesContext = context.getSearchExpressionContext().getFacesContext();
facesContext.getViewRoot().visitTree(VisitContext.createVisitContext(facesContext), (visit, component) -> {
if (component instanceof UIInput input && !input.isValid()) {
context.invokeContextCallback(input);
}
return VisitResult.ACCEPT;
});
context.setKeywordResolved(true);
}
}
package org.example.bookshelf.search;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIInput;
import jakarta.faces.component.search.SearchExpressionContext;
import jakarta.faces.component.search.SearchKeywordContext;
import jakarta.faces.component.search.SearchKeywordResolver;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitResult;
/** {@code @inputs}: every input below the base component. */
public class InputsKeywordResolver extends SearchKeywordResolver {
@Override
public boolean isResolverForKeyword(SearchExpressionContext context, String keyword) {
return "inputs".equals(keyword);
}
@Override
public void resolve(SearchKeywordContext context, UIComponent current, String keyword) {
var facesContext = context.getSearchExpressionContext().getFacesContext();
current.visitTree(VisitContext.createVisitContext(facesContext), (visit, component) -> {
if (component instanceof UIInput) {
context.invokeContextCallback(component);
}
return VisitResult.ACCEPT;
});
context.setKeywordResolved(true);
}
}
package org.example.bookshelf.search;
import jakarta.faces.application.Application;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.PostConstructApplicationEvent;
import jakarta.faces.event.SystemEvent;
import jakarta.faces.event.SystemEventListener;
/** Registers the {@code @inputs} keyword in code; {@code @invalid} is declared in faces-config.xml. */
public class RegisterResolver implements SystemEventListener {
@Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
Application application = ((PostConstructApplicationEvent) event).getApplication();
application.addSearchKeywordResolver(new InputsKeywordResolver());
}
@Override
public boolean isListenerForSource(Object source) {
return source instanceof Application;
}
}
Source: Jakarta Faces 4.1 API: SearchKeywordResolver, Jakarta Faces 4.1 API: SearchKeywordContext and Jakarta Faces 4.1 API: Application.addSearchKeywordResolver
A resolver is registered in one of two ways, both used above. In faces-config.xml (/faces-config/application/search-keyword-resolver
in the specification’s list of application configuration elements) or with Application.addSearchKeywordResolver, which
adds to the head of the chain, cannot be undone, and throws IllegalStateException after the first request; the
PostConstructApplicationEvent listener above is the usual place. Per the API documentation of
Application.getSearchKeywordResolver() the chain is: the resolvers declared in the configuration, then those added in
code, then the built-in ones.
<?xml version="1.0" encoding="UTF-8"?>
<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">
<application>
<search-expression-handler>org.example.bookshelf.search.DiagnosticSearchExpressionHandler</search-expression-handler>
<search-keyword-resolver>org.example.bookshelf.search.InvalidKeywordResolver</search-keyword-resolver>
<system-event-listener>
<system-event-listener-class>org.example.bookshelf.search.RegisterResolver</system-event-listener-class>
<system-event-class>jakarta.faces.event.PostConstructApplicationEvent</system-event-class>
<source-class>jakarta.faces.application.Application</source-class>
</system-event-listener>
</application>
</faces-config>
Source: Jakarta Faces 4.1 specification, 11.3 Application Configuration Resources and Jakarta Faces 4.1 API: Application
Observed with the two resolvers:
-
h:message for="@invalid"showed the message of the first invalid input, as in the review view above, on both. -
@form:@inputsresolved to every input in the form in the programmatic probe (clientIdsof all five inputs of the probe form). Inf:ajaxit expanded to every input on MyFaces, but only to the first (search:title) on Mojarra: Mojarra’s renderer keeps the first component delivered for a token, MyFaces' keeps all of them, and my resolver does not look atRESOLVE_SINGLE_COMPONENT(the built-in@iddoes, and stops at the first match on both). A resolver that should behave the same everywhere checks the hint. Do not rely on a multi-match keyword inf:ajaxunless you test both. -
A keyword that matches nothing is an error where the hints do not include
IGNORE_NO_RESULT:execute="@form:@invalid"failed the page on MyFaces, and Mojarra sent a token that the server could not use. -
PrimeFaces uses the same extension point for its own keywords (PrimeFaces Search Expressions).
Decorating the Handler
The handler itself is replaceable. A class that extends SearchExpressionHandlerWrapper and has a constructor that takes the
handler it wraps can be named in the search-expression-handler element (above); two wrappers registered in different
configuration files were both active in the runs. The
example below logs each expression that matched nothing and then lets the default behaviour continue: it makes the silent
Mojarra case visible.
package org.example.bookshelf.search;
import jakarta.faces.component.search.ComponentNotFoundException;
import jakarta.faces.component.search.SearchExpressionContext;
import jakarta.faces.component.search.SearchExpressionHandler;
import jakarta.faces.component.search.SearchExpressionHandlerWrapper;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** Logs every expression that cannot be resolved, then lets the default behaviour go on. */
public class DiagnosticSearchExpressionHandler extends SearchExpressionHandlerWrapper {
private static final Logger LOG = Logger.getLogger(DiagnosticSearchExpressionHandler.class.getName());
public DiagnosticSearchExpressionHandler(SearchExpressionHandler wrapped) {
super(wrapped);
}
@Override
public String resolveClientId(SearchExpressionContext context, String expression) {
try {
return super.resolveClientId(context, expression);
} catch (ComponentNotFoundException e) {
report(context, expression);
throw e;
}
}
@Override
public List<String> resolveClientIds(SearchExpressionContext context, String expressions) {
try {
return super.resolveClientIds(context, expressions);
} catch (ComponentNotFoundException e) {
report(context, expressions);
throw e;
}
}
private void report(SearchExpressionContext context, String expression) {
LOG.log(Level.WARNING, "Search expression \"{0}\" matched nothing, source component \"{1}\"",
new Object[] {expression, context.getSource().getId()});
}
}
Source: Jakarta Faces 4.1 API: SearchExpressionHandlerWrapper and Jakarta Faces 4.1 API: ComponentNotFoundException
With the wrapper Mojarra logged WARNING: Search expression "nosuch" matched nothing, source component "go" on a page whose
f:ajax was otherwise silently accepted. The wrapper overrides resolveClientId because Mojarra’s f:ajax renderer
calls that one, and resolveClientIds because MyFaces' calls that one; override the methods your callers use. The factory that creates the contexts, SearchExpressionContextFactory, is decorated the same way
through the search-expression-context-factory element of the 4.1 configuration schema (the prose of the specification
spells it search-expression-context-kit-factory; I did not register one, so which spelling a runtime accepts is not
verified).
PrimeFaces Search Expressions
PrimeFaces 16 builds on the facility: its documentation calls it the Search Expression Framework and says that PrimeFaces
enhances the Faces search expressions with its own keywords. process, update, trigger and similar attributes of
PrimeFaces components take them. The jar ships its own keyword resolvers (the package org.primefaces.expression has
classes for @(selector), @row, @form, the observer and @widgetVar), and PrimeFaces resolves its attributes by calling the
handler (the stack trace of a failure ran through SearchExpressionUtils into resolveClientIds). The keywords from the PrimeFaces documentation, 16.0.0:
| Keyword | Meaning |
|---|---|
|
The Faces keywords as above. |
|
The n-th row of a data table; the last resolved component must be a |
|
The component with that |
|
PrimeFaces Selectors (PFS): a jQuery selector evaluated in the browser. |
|
Targets an event, as described in the Event/Observer pattern of the PrimeFaces documentation (not exercised here). |
Chaining works as in Faces (@form:@parent, @composite:mybuttonid), and the documentation adds that the framework applies
wherever one component references another, not only to partial processing and updates. The Bookshelf view below uses a
selector, a widget variable, a chain and a row:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:p="http://primefaces.org/ui">
<h:head><title>Bookshelf search with PrimeFaces</title></h:head>
<h:body>
<h:form id="search">
<p:inputText id="title" styleClass="criteria" value="#{catalogueBean.title}"/>
<p:inputText id="author" styleClass="criteria" value="#{catalogueBean.author}"/>
<p:commandButton id="byClass" value="By style class" action="#{catalogueBean.search}"
process="@(.criteria)" update="results"/>
<p:commandButton id="byWidget" value="By widget" action="#{catalogueBean.search}"
process="@this title" update="@widgetVar(resultsPanel)"/>
<p:commandButton id="byChain" value="By chain" action="#{catalogueBean.search}"
process="@form:@child(0)" update="@form:results"/>
<p:commandButton id="oneRow" value="One row" action="#{catalogueBean.search}"
process="@this" update="books:@row(0)"/>
<p:panel id="results" widgetVar="resultsPanel" header="Results">
<p:dataTable id="books" var="book" value="#{catalogueBean.results}">
<p:column headerText="Title"><h:outputText value="#{book.title}"/></p:column>
</p:dataTable>
</p:panel>
</h:form>
</h:body>
</html>
Observed on Mojarra and MyFaces with PrimeFaces 16.0.0 (the same on both):
-
process="@(.criteria)"was sent to the browser and resolved there: the request carriedsearch:title search:author. A selector is therefore not subject to the naming container limits, but it only matches components that have an explicit id in the markup. The documentation says the same and links to its list of limitations. -
@form:@child(0)and@form:resultsresolved on the server tosearch:titleandsearch:results;@this titlebecamesearch:byWidget,search:title(PrimeFaces joins with commas). -
A PrimeFaces selector cannot be combined with other keywords:
@(.field):@parentfailed the page withFacesException: jQuery selectors aka PFS are not supported on the server side. -
@id(inside)from a button, an unknown id such asnosuch, and@bogusfailed the rendering with aComponentNotFoundException/FacesExceptionon both implementations, so the strictness of PrimeFaces components does not depend on the implementation, unlikef:ajax(Mojarra and MyFaces Compared).@row(1)outside a table failed with "The last resolved component must be instance of UIData to support @row". -
books:@row(0)resolved to a generated id inside the row (search:books:0:j_idt6on Mojarra,search:books:0:j_id_gon MyFaces). It is documented but gives an id that is not stable; I would not use it to update a cell. -
Comma and space both separated expressions (
q,randq rgave the same two ids). -
@widgetVar(resultsPanel)was sent to the browser unresolved. In jsdom no widget was created, so the request fell back to another value; I could not run it end to end and make no claim about it.@obs(event)and@compositeinside a composite were not exercised either.
Mojarra and MyFaces Compared
| Case (Mojarra 4.1.17 / MyFaces 4.1.4) | Mojarra | MyFaces |
|---|---|---|
How |
|
|
Separators in |
white space only ( |
white space and comma |
Expression that matches nothing, in |
caught; the token is sent on unresolved (silent) |
|
Keyword that delivers several components, in |
the first one only |
all of them (the built-in |
|
sent on unresolved, ignored by the server |
|
Relative id outside the naming container ( |
found through a client-id lookup from the view root |
found by walking up the naming containers |
Absolute id with a row index ( |
resolved |
|
|
|
|
Order of the ids in the request |
source first |
source last |
|
kept for |
replaced by the client id |
|
|
|
|
silent |
|
The rest, including all the keywords of the table in The Keywords, the leaf rule, the composite results, SKIP_VIRTUAL_COMPONENTS
and the labels, behaved identically.
What Changed per Version
| Version | Change |
|---|---|
JSF 2.0 to 2.2 |
|
Faces 2.3 |
The enhanced component search facility (issue 1238 in the specification’s change log): the package |
Faces 3.0 |
|
Faces 4.0 |
"Improve |
Faces 4.1 |
Nothing in the change log concerns search expressions. |
Faces 5.0 (upcoming) |
Not examined for this page (latest milestone |
Where the Books Are Outdated
PrimeFaces Cookbook, Second Edition (Çalışkan and Varaksin, 2015) treats process, update and its own search
expressions in its first chapters, for PrimeFaces 5.x on JSF 2.2. When it was written the PrimeFaces framework was a library
feature; since Faces 2.3 the core specification owns the facility and PrimeFaces extends it, so the same expressions now work
in f:ajax and in the standard components (labels, messages) too. Its widget and selector keywords remain PrimeFaces
specific, and PrimeFaces 16 documents more keywords than a book of that age can show (@row, @obs, @widgetVar, chaining).
JavaServer Faces 2.0: The Complete Reference (Burns and Schalk, 2010) and the JSF 2.0 Cookbook (Leonard, 2010) were
published before Faces 2.3, so they cannot describe the standard keywords beyond the four of f:ajax, chaining, the handler
or custom keywords, and their code uses javax.faces. The Jakarta EE Tutorial itself still lists four keywords. Everything
above was checked on 4.1.
Pitfalls
-
Writing
@id(x)from a button without an anchor (@form:@id(x),@root:@id(x)); it works on neither implementation for its own reason (Chaining and the @id Keyword). -
Using
@rootalone asexecuteorrender(Chaining and the @id Keyword): use@all. -
Separating expressions with commas: Mojarra does not split them (execute and render in f:ajax).
-
Relying on Mojarra’s silence: a typo in
renderis not an error there; add the wrapper of Decorating the Handler or test on both implementations. -
Rendering a
ui:repeat, a naming container or a composite by its id: none of them writes an element (Inside Tables and ui:repeat, Inside Composite Components). -
Absolute ids with row indexes (Inside Tables and ui:repeat).
-
A keyword resolver that tests
"@name"instead of"name"(Custom Keywords). -
A custom keyword that reads the state of the postback in
f:ajax; it is resolved at render of the page (When an Expression Is Resolved). -
A keyword that yields several components in
f:ajax: all of them on MyFaces, the first on Mojarra. -
Wrapping the handler or registering a keyword after the first request:
Application.addSearchKeywordResolverthrowsIllegalStateException.
References
-
Jakarta Faces 4.1 API: package jakarta.faces.component.search
-
Jakarta Faces 4.1 specification, 3.1.8 Component Tree Navigation
-
Jakarta Faces 4.1 specification, 11.3 Application Configuration Resources
-
Jakarta Faces 4.1 specification, B.4 Changes between 2.2 and 2.3
-
Jakarta EE Tutorial: Using Ajax with Jakarta Faces Technology
-
Çalışkan, Varaksin: PrimeFaces Cookbook, Second Edition, Packt, 2015, publisher page
-
Burns, Schalk: JavaServer Faces 2.0: The Complete Reference, McGraw-Hill, 2010, publisher page
-
Leonard: JSF 2.0 Cookbook, Packt, 2010, publisher page