Client Behaviors
|
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). Client behaviors exist since JSF 2.0 and the model has not changed since, so everything below also applies
to Faces 4.0 apart from the jakarta.* names, but only 4.1 was run. The code 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, and the scripts were driven
in headless Chrome 154 (clicking, accepting or dismissing the confirm dialog, counting the POST requests). Everything marked
as observed happened in those runs on 2026-10-08. PrimeFaces 16 is named once as an example of a third-party behavior and was
not run.
A client behavior is an object that you attach to one client-side event of a component. At render time the component asks
it for a piece of JavaScript and writes that script into the event handler it renders. When the user triggers the event in
the browser, the script runs. That is all a behavior has to do, and it is how the standard f:ajax tag works: it creates an
AjaxBehavior, attaches it to the component it is nested in, and the behavior’s script sends the Ajax request. A custom
behavior is therefore the right tool when you want a reusable piece of client-side logic that you can attach to many
components with one nested tag instead of copying an onclick attribute around: a confirmation dialog, a tracking
call, a keyboard shortcut, a tooltip trigger. A behavior may also post back to the server and queue events of its own, which
the page shows with a click tracker.
The Bookshelf scenario uses two small behaviors: bs:confirm asks "are you sure?" before a review is deleted, and
bs:track tells the server when a visitor follows a link to the publisher’s page. A third piece, the bc:pill
component, shows the other side of the contract: a component that accepts behaviors.
The Behavior Model
The model lives in the package jakarta.faces.component.behavior (plus a few types in jakarta.faces.event,
jakarta.faces.render and jakarta.faces.view.facelets). Section 3.7 of the specification defines it; this table lists
every type that a behavior author meets.
| Type | Role |
|---|---|
|
The root contract. It has one method, |
|
A convenience base class that implements |
|
Extends |
|
Extends |
|
The component side: |
|
An abstract class created with the static |
|
An enum with one constant, |
|
In |
|
The only concrete behavior the specification defines. Its behavior id and renderer type are both
|
|
The server-side event and listener bases for behaviors that deliver events ( |
|
Register a behavior or a client behavior renderer by annotation. |
Source: Jakarta Faces 4.1 specification, 3.7 Component Behavior Model and ClientBehavior API documentation
Two details are worth knowing before the first example. First, the specification says (3.7.1) that the standard HTML
components are all "client behavior-ready". The h: tags found on both implementations are not: the next
section gives the list, and a behavior nested in a tag that is not a holder fails. Second, nothing in the model is specific to
confirmation dialogs or to Ajax. A behavior’s script is any JavaScript statement sequence, and whether the behavior takes part
in the server-side lifecycle is a separate decision (decode, events and the SUBMITTING hint, covered below).
Where a Behavior Sits in the Lifecycle
A behavior touches the request processing lifecycle (see the phases) in four places:
-
View build (Restore View, or the first Render Response). The Facelets tag for the behavior creates the behavior with
Application.createBehavior(id)and callsaddClientBehavior(eventName, behavior)on the parent component. The tag runs every time the view is built: on the initial request, and again on each postback because the view is rebuilt from the templates before the saved state is applied (partial state saving). Properties set from tag attributes are therefore set again on every build. The state section below returns to this. -
Render Response. The component’s renderer, or the component itself, asks each behavior of an event for its script through
getScript(ClientBehaviorContext)and writes the script into the DOM event handler attribute. -
In the browser. The script runs when the DOM event fires. A script that returns
falsestops the scripts after it in the chain and, for a submit button, stops the form submission (spec 14.6,faces.util.chain). -
Apply Request Values, only for behaviors that post back. When the script sends a request, it must include the name of the behavior event in the parameter
jakarta.faces.behavior.eventand the id of the source component injakarta.faces.source. The component’s renderer reads both, finds the behaviors attached to that event, and, if the source matches its own client id, callsdecode(context, component)on each of them.decodemay queueBehaviorEventobjects withcomponent.queueEvent(...); the event is delivered in the phase set on it (AjaxBehavioruses Apply Request Values when immediate and Invoke Application otherwise). The component then calls the behavior’sbroadcastand the behavior hands the event to its listeners.
The renderkit documentation states the decode rule and adds one more: behavior decoding applies to components that are
readonly but not to components that are disabled (not run). Source for the rule:
Standard HTML RenderKit documentation
The sequence below is the tracking behavior of this page, the one that does post back (the code follows in the renderer section):
The request that the script sends is an ordinary Faces Ajax request, so it carries the form’s view state. Observed on both
implementations: the same POST sent by hand without jakarta.faces.ViewState was answered with HTTP 200 and a fresh view,
the behavior was not decoded and the listener did not run.
Which Components Accept Behaviors
A component accepts behaviors if it implements ClientBehaviorHolder. Its event names are the logical events a behavior may
attach to. For HTML components these are DOM event names without the on prefix (click, keyup, change), plus two
logical names: action for components that are an ActionSource (the buttons and links that submit) and valueChange
for components that are an EditableValueHolder (the inputs). The default event is what you get when the nested tag has
no event attribute.
The holders were listed by creating each registered component type through Application.createComponent and reading
getEventNames() and getDefaultEventName() (the sets are identical on both implementations unless noted):
| Tag (component class) | Default event | Event names |
|---|---|---|
|
|
|
|
|
the same list without |
|
|
|
|
|
|
|
|
as the inputs, including |
|
Mojarra |
|
|
Mojarra |
as |
|
none |
|
|
none |
|
|
none |
as |
|
none |
the form list plus |
Not holders on either implementation: h:outputText, h:outputFormat, h:message, h:messages, h:column, h:head,
h:inputHidden and h:commandScript. To attach a behavior to text, wrap it: an h:panelGroup (a span by default, a div with
layout="block") is a holder. The API documentation also lists PassthroughElement and UIWebsocket (the component of f:websocket) among the
implementing classes; the probe found UIWebsocket and the passthrough element only on MyFaces, because Mojarra does not create them by component type,
so that part of the list is from the documentation, not from a run.
Nesting a behavior in a tag that is not a holder fails at render time on both implementations. Mojarra reports "Parent not an instance of ClientBehaviorHolder"; MyFaces reports "Parent not composite component or an instance of ClientBehaviorHolder".
Event names are checked differently. An event attribute that is an expression fails on both implementations, because the
attribute must be a literal: Mojarra reports "The 'event' attribute for behavior tag must be a literal", MyFaces that the event
name #{param.e} was not found on the component. A literal name that the component does not list (event="nonsense" on a
button) is accepted silently by Mojarra, which answers with HTTP 200 and a button that has no script, and rejected by MyFaces
with "eventName nonsense not found on component instance" (HTTP 500). A tag without event inside a holder that has no default
event fails on both: Mojarra reports "The event name is not defined", MyFaces "null eventName could not be defined for client
behavior" (observed with an h:panelGroup).
The rendered attribute is on plus the DOM name, with the two logical names mapped: action on a button or link and
click both produce onclick, and the default valueChange of an input produces onchange (observed for the button, the
link and h:inputText). There is one trap. On an h:outputLink Mojarra’s default event is action, and for that
component the renderer writes the attribute onaction, which no browser fires. Observed: a confirm behavior without an
event attribute rendered onaction="return confirm('Leave?')", and the same happened to a plain f:ajax (rendered
onaction="mojarra.ab(this,event,'action',0,0)"). MyFaces has no default event for the component and fails with "null
eventName could not be defined for client behavior". Both are avoided by always writing event="click" on an
h:outputLink or h:link.
Attaching a Behavior
There are three ways to get a behavior onto a component.
A nested tag. A behavior is exposed to Facelets as a tag, declared in a tag library (shown in the next section). The tag is
nested in the holder, its optional event attribute names the event, and every other attribute is set on the behavior as a
bean property. The tag also understands binding, a value expression that supplies the behavior instance: the tag handler
uses the instance the expression returns, and creates one and stores it through the expression when it returns null. The names
event, binding and for are reserved: the handler does not set them as properties (read in Mojarra’s
BehaviorTagHandlerDelegateImpl; for serves composite components). Observed on both implementations:
<bs:confirm binding="#{attachBean.bound}" .../> rendered the expected script. Because the handler uses whatever instance
the expression returns, a binding to a bean in a longer scope would hand the same behavior object to every view that references it
(read in the source, not run), so keep the bean request scoped.
f:ajax. The standard tag, the same mechanism (it is the AjaxBehavior). Because it is a behavior, it composes with
yours on the same event, as the Ajax delete button below does.
Java. Create the behavior with Application.createBehavior(id) and attach it with addClientBehavior. The usual place is
a postAddToView listener on the component, so that the behavior is added once the component is in the tree:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head>
<title>Attach from Java</title>
</h:head>
<h:body>
<h:form id="attach">
<h:commandButton id="clear" value="Clear" action="#{reviewsBean.reset}">
<f:event type="postAddToView" listener="#{attachBean.attach}"/>
</h:commandButton>
</h:form>
</h:body>
</html>
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.behavior.ClientBehavior;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.ComponentSystemEvent;
import jakarta.inject.Inject;
import jakarta.inject.Named;
@Named
@RequestScoped
public class AttachBean {
@Inject
FacesContext context;
private ClientBehavior bound;
public void attach(ComponentSystemEvent event) {
UIComponent component = event.getComponent();
ConfirmBehavior behavior = (ConfirmBehavior) context.getApplication().createBehavior("bookshelf.confirm");
behavior.setMessage("Clear all reviews?");
((ClientBehaviorHolder) component).addClientBehavior("click", behavior);
}
public ClientBehavior getBound() {
return bound;
}
public void setBound(ClientBehavior bound) {
this.bound = bound;
}
}
The bound property of the bean is what the binding attribute above pointed at. Observed on both implementations: the button had exactly one confirm script, both before and after a postback (the listener
runs again on each build). createBehavior throws a FacesException for an id that is not registered; observed through a
tag whose behavior-id had no registration: Mojarra reported "Expression Error: Named Object: bookshelf.ghost not found.",
MyFaces "Could not find any registered behavior-class for behaviorId : bookshelf.ghost" (both HTTP 500).
A Custom Confirm Behavior
The first behavior is bs:confirm: the browser’s confirm dialog before the event’s normal work. Cancelling the dialog
stops the click. It is a script-only behavior, so it extends ClientBehaviorBase and overrides getScript instead of
naming a renderer.
The message is text from the page author, and in the next section it becomes text from the database (a book title), so it
cannot be pasted into a JavaScript string literal. A title such as O'Reilly's "Guide" </script> would end the literal and
run whatever follows. The helper below escapes the characters that matter in a single-quoted literal that is itself inside an HTML
attribute or a script element. Faces escapes the attribute value for HTML afterwards, but that is a different language.
package org.example.bookshelf.web;
/** Escapes text for use inside a single-quoted JavaScript string literal that is itself inside an HTML attribute or script. */
final class JsStrings {
private JsStrings() {
}
static String escape(String text) {
StringBuilder out = new StringBuilder(text.length() + 16);
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
switch (c) {
case '\\' -> out.append("\\\\");
case '\'' -> out.append("\\'");
case '"' -> out.append("\\u0022");
case '<' -> out.append("\\u003c");
case '>' -> out.append("\\u003e");
case '&' -> out.append("\\u0026");
case '\n' -> out.append("\\n");
case '\r' -> out.append("\\r");
case '
' -> out.append("\\u2028");
case '
' -> out.append("\\u2029");
default -> {
if (c < 0x20) {
out.append(String.format("\\u%04x", (int) c));
} else {
out.append(c);
}
}
}
}
return out.toString();
}
}
The behavior itself, registered with @FacesBehavior under the id bookshelf.confirm:
package org.example.bookshelf.web;
import jakarta.faces.component.behavior.ClientBehaviorBase;
import jakarta.faces.component.behavior.ClientBehaviorContext;
import jakarta.faces.component.behavior.FacesBehavior;
import jakarta.faces.context.FacesContext;
/** A client behavior that asks "OK or Cancel?" before the event's default work (a postback, an Ajax request) runs. */
@FacesBehavior("bookshelf.confirm")
public class ConfirmBehavior extends ClientBehaviorBase {
private String message = "Are you sure?";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
clearInitialState();
}
@Override
public String getScript(ClientBehaviorContext context) {
if (message == null || message.isBlank()) {
return null;
}
return "return confirm('" + JsStrings.escape(message) + "')";
}
@Override
public Object saveState(FacesContext context) {
if (initialStateMarked()) {
return null;
}
return new Object[] {super.saveState(context), message};
}
@Override
public void restoreState(FacesContext context, Object state) {
if (state == null) {
return;
}
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
message = (String) values[1];
}
}
-
getScriptreturnsnullwhen there is nothing to say; the Javadoc allows that and the component then renders no script for the event. -
The script is
return confirm('...'). Thereturnis what lets afalseresult cancel the default action of a submit button. -
setMessagecallsclearInitialState(), as every setter of a state-holding behavior must (see state saving).
Source: Jakarta Faces 4.1 specification, 3.7.9 ClientBehaviorBase and FacesBehavior API documentation
The tag library declares the tag. A behavior tag has a tag-name and a behavior element whose behavior-id is the id
used in the annotation (the optional handler-class is used in the next section):
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib 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-facelettaglibrary_4_1.xsd"
version="4.1">
<namespace>urn:bookshelf:behaviors</namespace>
<tag>
<tag-name>confirm</tag-name>
<behavior>
<behavior-id>bookshelf.confirm</behavior-id>
</behavior>
</tag>
<tag>
<tag-name>confirmDynamic</tag-name>
<behavior>
<behavior-id>bookshelf.confirmDynamic</behavior-id>
<handler-class>org.example.bookshelf.web.ExpressionBehaviorHandler</handler-class>
</behavior>
</tag>
<tag>
<tag-name>track</tag-name>
<behavior>
<behavior-id>bookshelf.track</behavior-id>
<handler-class>org.example.bookshelf.web.ExpressionBehaviorHandler</handler-class>
</behavior>
</tag>
</facelet-taglib>
A library under WEB-INF is listed in web.xml, as a semicolon-separated list of paths starting with a slash; a library
inside a JAR is found by itself when the file is META-INF/*.taglib.xml (spec chapter 10):
<context-param>
<param-name>jakarta.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/bookshelf.taglib.xml</param-value>
</context-param>
Using it on the Bookshelf reviews page, once with a plain submit and once combined with f:ajax:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bs="urn:bookshelf:behaviors">
<h:head>
<title>Reviews</title>
</h:head>
<h:body>
<h:form id="reviews">
<h:dataTable id="list" value="#{reviewsBean.reviews}" var="r">
<h:column>#{r.title}</h:column>
<h:column>
<h:commandButton id="delete" value="Delete" action="#{reviewsBean.delete(r)}">
<bs:confirm message="Delete this review?"/>
</h:commandButton>
</h:column>
<h:column>
<h:commandButton id="deleteAjax" value="Delete (Ajax)" action="#{reviewsBean.delete(r)}">
<bs:confirm message="Delete this review?"/>
<f:ajax render="@form"/>
</h:commandButton>
</h:column>
</h:dataTable>
</h:form>
</h:body>
</html>
The rendered buttons differ between implementations but do the same thing (copied from the responses):
<input id="reviews:list:0:delete" type="submit" name="reviews:list:0:delete" value="Delete" onclick="return confirm('Delete this review?')" />
<input id="reviews:list:0:deleteAjax" type="submit" name="reviews:list:0:deleteAjax" value="Delete (Ajax)" onclick="faces.util.chain(this,event,'return confirm(\'Delete this review?\')','mojarra.ab(this,event,\'action\',0,\'@form\')');event.preventDefault()" />
<input id="reviews:list:0:delete" name="reviews:list:0:delete" type="submit" value="Delete" onclick="return faces.util.chain(this, event,function(event){return confirm('Delete this review?')});" />
<input id="reviews:list:0:deleteAjax" name="reviews:list:0:deleteAjax" type="submit" value="Delete (Ajax)" onclick="faces.util.chain(this, event,function(event){return confirm('Delete this review?')}, function(event){myfaces.ab(this,event,'action','','@form',{})}); return false;" />
With a single behavior on a plain button, Mojarra renders the script alone; with f:ajax next to it, both implementations
wrap the scripts in faces.util.chain and suppress the default submit, so Ajax replaces it. In the Ajax button the confirm
script comes first (the order of the nested tags). Observed in Chrome on both implementations, for the plain and the Ajax
button: dismissing the dialog sent no request and left the three reviews, accepting it sent exactly one POST and removed the
first review.
The Ajax button uses render="@form" because of a difference outside the scope of this page: written as the table’s id
(list or :reviews:list), Mojarra resolved the render target of a button in a table row to the row
(reviews:list:0, seen in the mojarra.ab call) and refreshed only that row, whereas MyFaces refreshed the table.
The Ajax page returns to this.
A confirm dialog is a courtesy to the user, not a security control. The request that deletes a review can be sent without the
script, so delete must authorize the caller on the server like any other action.
State Saving and Behaviors
BehaviorBase implements PartialStateHolder. With partial state saving (the default) the framework rebuilds the view from
the Facelets templates on every postback and saves only what changed after the initial state was marked. The tag handler runs
again during that rebuild, so the properties set from tag attributes are set again by the page itself and need no saving. State
that is changed afterwards, in Java, has to be saved, and the contract is:
-
call
clearInitialState()in each setter, so that the behavior is no longer in its initial state and the framework asks for its state; -
return
nullfromsaveStatewhileinitialStateMarked()istrue; -
save the superclass state and your own fields otherwise, and restore them in
restoreState.
ConfirmBehavior above follows this. The property survives full state saving as well
(jakarta.faces.PARTIAL_STATE_SAVING=false, deprecated for removal since 4.1): observed on both implementations, the
rendered script of bs:confirm was identical before and after a postback with it switched off. A behavior without
saveState and restoreState loses its properties in that mode: the next section’s bs:confirmDynamic showed the
failure before the two methods were added (the script, and so the confirmation, silently disappeared from every button after the
first postback; getScript returned null because the message was null).
A ValueExpression is saved with UIComponentBase.saveAttachedState and restored with restoreAttachedState, as the
code in the next section does. Keep only values that can be saved (serializable values and attached objects); never keep a
reference to the FacesContext or to a component.
Expressions in Behavior Attributes: a Message per Row
The reviews page above has a generic message. A better dialog names the review: "Delete the review of Dune?". Written with the obvious tag, it does not work:
<h:commandButton value="Delete" action="#{reviewsBean.delete(r)}">
<bs:confirm message="Delete the review of #{r.title}?"/>
</h:commandButton>
The cause is how Facelets sets a tag attribute on a behavior. Unlike a component, a behavior does not receive the attribute as a
value expression: the tag handler evaluates the expression once, when it applies the tag, and calls setMessage(String) with the
result. The tag is applied when the view is built, not once per table row, so r is not defined then. Observed in the rendered
buttons of both implementations: onclick="return confirm('Delete the review of ?')" on every row. EL that does not depend on
the row (a bundle entry, a bean property) works, and it is evaluated at build time.
To get the evaluation at render time, per row, the behavior needs the unevaluated expression. A setter that takes
ValueExpression is not enough by itself: with the default tag handler the same page failed on both implementations with "Cannot convert [Delete the review of ?] of type [class java.lang.String] to [class jakarta.el.ValueExpression]" (Mojarra’s message; MyFaces
gave the same cause). The default rule for a bean property evaluates the attribute and then tries to coerce the result.
The fix is a small tag handler that adds a Facelets MetaRule for setters of type ValueExpression (and, for the click
tracker below, MethodExpression). A handler is declared with the handler-class element of the tag, as in the tag library
above, and is a subclass of BehaviorHandler that overrides createMetaRuleset. MetaRuleset.addRule appends a rule,
and the rules added last are tried first (read in Mojarra’s MetaRulesetImpl.finish).
package org.example.bookshelf.web;
import jakarta.el.MethodExpression;
import jakarta.el.ValueExpression;
import jakarta.faces.event.BehaviorEvent;
import jakarta.faces.view.facelets.BehaviorConfig;
import jakarta.faces.view.facelets.BehaviorHandler;
import jakarta.faces.view.facelets.FaceletContext;
import jakarta.faces.view.facelets.MetaRule;
import jakarta.faces.view.facelets.MetaRuleset;
import jakarta.faces.view.facelets.Metadata;
import jakarta.faces.view.facelets.MetadataTarget;
import jakarta.faces.view.facelets.TagAttribute;
import jakarta.faces.view.facelets.TagAttributeException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* A tag handler for behaviors whose setters take a ValueExpression or a MethodExpression: the attribute is handed over
* unevaluated, so the behavior can evaluate it each time it renders its script or receives an event.
*/
public class ExpressionBehaviorHandler extends BehaviorHandler {
public ExpressionBehaviorHandler(BehaviorConfig config) {
super(config);
}
@Override
protected MetaRuleset createMetaRuleset(Class type) {
return super.createMetaRuleset(type).addRule(new ExpressionRule());
}
private static final class ExpressionRule extends MetaRule {
@Override
public Metadata applyRule(String name, TagAttribute attribute, MetadataTarget target) {
Method setter = target.getWriteMethod(name);
if (setter == null) {
return null;
}
Class<?> parameter = setter.getParameterTypes()[0];
if (parameter != ValueExpression.class && parameter != MethodExpression.class) {
return null;
}
return new Metadata() {
@Override
public void applyMetadata(FaceletContext ctx, Object instance) {
try {
Object expression = parameter == ValueExpression.class
? attribute.getValueExpression(ctx, Object.class)
: attribute.getMethodExpression(ctx, void.class, new Class[] {BehaviorEvent.class});
setter.invoke(instance, expression);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new TagAttributeException(attribute, e);
}
}
};
}
}
}
The behavior then keeps the expression, evaluates it each time getScript runs (the table is being iterated at that
moment, so r is bound) and saves it:
package org.example.bookshelf.web;
import jakarta.el.ValueExpression;
import jakarta.faces.component.UIComponentBase;
import jakarta.faces.component.behavior.ClientBehaviorBase;
import jakarta.faces.component.behavior.ClientBehaviorContext;
import jakarta.faces.component.behavior.FacesBehavior;
import jakarta.faces.context.FacesContext;
/** Like ConfirmBehavior, but the message is a ValueExpression evaluated every time the script is rendered. */
@FacesBehavior("bookshelf.confirmDynamic")
public class DynamicConfirmBehavior extends ClientBehaviorBase {
private ValueExpression message;
public void setMessage(ValueExpression message) {
this.message = message;
clearInitialState();
}
@Override
public String getScript(ClientBehaviorContext context) {
if (message == null) {
return null;
}
Object text = message.getValue(context.getFacesContext().getELContext());
return text == null ? null : "return confirm('" + JsStrings.escape(text.toString()) + "')";
}
@Override
public Object saveState(FacesContext context) {
if (initialStateMarked()) {
return null;
}
return new Object[] {super.saveState(context), UIComponentBase.saveAttachedState(context, message)};
}
@Override
public void restoreState(FacesContext context, Object state) {
if (state == null) {
return;
}
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
message = (ValueExpression) UIComponentBase.restoreAttachedState(context, values[1]);
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bs="urn:bookshelf:behaviors">
<h:head><title>table</title></h:head>
<h:body>
<h:form id="f">
<h:dataTable id="t" value="#{reviewsBean.reviews}" var="r">
<h:column><h:outputText value="#{r.title}"/></h:column>
<h:column>
<h:commandButton id="static" value="static" action="#{reviewsBean.delete(r)}">
<bs:confirm message="Delete the review of #{r.title}?"/>
</h:commandButton>
<h:commandButton id="dynamic" value="dynamic" action="#{reviewsBean.delete(r)}">
<bs:confirmDynamic message="Delete the review of #{r.title}?"/>
</h:commandButton>
</h:column>
</h:dataTable>
</h:form>
</h:body>
</html>
Observed on both implementations: the static buttons rendered Delete the review of ? and the dynamic buttons
Delete the review of Dune?, Delete the review of O\'Reilly\'s "Guide" </script>? and
Delete the review of Emma?. In Chrome the second dialog read Delete the review of O'Reilly's "Guide" </script>? on both
implementations, so the escaping holds. The scripts also survived a postback with partial state saving and with full state
saving.
A Behavior with a Renderer: the Click Tracker
ClientBehaviorBase offers a second way to produce the script: return a renderer type from getRendererType() and let a
ClientBehaviorRenderer do the work. This is the same split as for components. It pays off when the script depends on the
render kit, or when you want the behavior class to stay a plain bean and the output logic to be replaceable; the standard
AjaxBehavior is built that way (its renderer type is its id). For a script-only behavior like confirm it is more code
than the class above.
The second Bookshelf behavior, bs:track, reports a click to the server in the background, for example when a visitor follows
the link to the publisher’s page. It needs the server side of the contract: a script that sends a request, a decode that
turns the request into an event, and a listener. The pieces are:
-
TrackBehavior(the behavior): the idbookshelf.track, the renderer typebookshelf.track, anisbnproperty, and alistenermethod expression; it overridesbroadcastto call that expression after the registered listeners. -
TrackBehaviorRenderer(registered with@FacesBehaviorRenderer): produces the script, anddecodequeues the event. -
TrackedEventandTrackedListener: the event and listener types (BehaviorEventandBehaviorListener).
package org.example.bookshelf.web;
import jakarta.el.MethodExpression;
import jakarta.faces.application.ResourceDependency;
import jakarta.faces.component.behavior.ClientBehaviorBase;
import jakarta.faces.component.behavior.FacesBehavior;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.BehaviorEvent;
/** Reports clicks to the server in the background; script and decoding are delegated to the renderer. */
@ResourceDependency(library = "jakarta.faces", name = "faces.js")
@FacesBehavior("bookshelf.track")
public class TrackBehavior extends ClientBehaviorBase {
public static final String RENDERER_TYPE = "bookshelf.track";
private String isbn;
private MethodExpression listener;
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public void setListener(MethodExpression listener) {
this.listener = listener;
}
public void addTrackedListener(TrackedListener trackedListener) {
addBehaviorListener(trackedListener);
}
@Override
public String getRendererType() {
return RENDERER_TYPE;
}
@Override
public void broadcast(BehaviorEvent event) throws AbortProcessingException {
super.broadcast(event);
if (event instanceof TrackedEvent && listener != null) {
listener.invoke(FacesContext.getCurrentInstance().getELContext(), new Object[] {event});
}
}
}
The renderer. The @FacesBehaviorRenderer attribute rendererType must equal the behavior’s renderer type; renderKitId
defaults to HTML_BASIC. The class needs a public no-argument constructor and one instance is shared by all requests, so it
holds no state:
package org.example.bookshelf.web;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.behavior.ClientBehavior;
import jakarta.faces.component.behavior.ClientBehaviorContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.render.ClientBehaviorRenderer;
import jakarta.faces.render.FacesBehaviorRenderer;
/** One instance serves every request, so it must be stateless. */
@FacesBehaviorRenderer(rendererType = TrackBehavior.RENDERER_TYPE)
public class TrackBehaviorRenderer extends ClientBehaviorRenderer {
@Override
public String getScript(ClientBehaviorContext context, ClientBehavior behavior) {
String isbn = ((TrackBehavior) behavior).getIsbn();
return "faces.ajax.request(this,event,{'jakarta.faces.behavior.event':'" + JsStrings.escape(context.getEventName())
+ "','isbn':'" + JsStrings.escape(isbn == null ? "" : isbn) + "'})";
}
@Override
public void decode(FacesContext context, UIComponent component, ClientBehavior behavior) {
String isbn = context.getExternalContext().getRequestParameterMap().get("isbn");
component.queueEvent(new TrackedEvent(context, component, behavior, isbn));
}
}
The event. BehaviorEvent requires the component and the behavior. setPhaseId decides when the event is delivered:
package org.example.bookshelf.web;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.behavior.Behavior;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.BehaviorEvent;
import jakarta.faces.event.FacesListener;
import jakarta.faces.event.PhaseId;
/** Queued when the browser reports a tracked click. */
public class TrackedEvent extends BehaviorEvent {
private static final long serialVersionUID = 1L;
private final String isbn;
public TrackedEvent(FacesContext context, UIComponent component, Behavior behavior, String isbn) {
super(context, component, behavior);
this.isbn = isbn;
setPhaseId(PhaseId.INVOKE_APPLICATION);
}
public String getIsbn() {
return isbn;
}
@Override
public boolean isAppropriateListener(FacesListener listener) {
return listener instanceof TrackedListener;
}
@Override
public void processListener(FacesListener listener) {
((TrackedListener) listener).processTracked(this);
}
}
package org.example.bookshelf.web;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.BehaviorListener;
public interface TrackedListener extends BehaviorListener {
void processTracked(TrackedEvent event) throws AbortProcessingException;
}
Source: Jakarta Faces 4.1 specification, 3.7.10 Behavior Event / Listener Model and FacesBehaviorRenderer API documentation
The bean that receives the events, and the tag declaration (the track tag in the tag library above uses the same handler as
confirmDynamic, so that the listener attribute arrives as a MethodExpression):
package org.example.bookshelf.web;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import jakarta.faces.event.BehaviorEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Named
@ApplicationScoped
public class TrackerBean {
private final List<String> hits = Collections.synchronizedList(new ArrayList<>());
public void onTracked(BehaviorEvent event) {
TrackedEvent tracked = (TrackedEvent) event;
hits.add(tracked.getComponent().getClientId() + " isbn=" + tracked.getIsbn() + " phase=" + tracked.getPhaseId());
}
public List<String> getHits() {
return hits;
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:bs="urn:bookshelf:behaviors">
<h:head>
<title>Publisher</title>
</h:head>
<h:body>
<h:form id="f">
<h:outputLink id="pub" value="https://example.org/dune" target="_blank">Publisher page
<bs:track event="click" isbn="9780441013593" listener="#{trackerBean.onTracked}"/>
</h:outputLink>
</h:form>
<pre id="hits">#{trackerBean.hits}</pre>
</h:body>
</html>
Source: BehaviorEvent API documentation
Observed on both implementations, in Chrome: clicking the link produced one POST with jakarta.faces.behavior.event=click,
jakarta.faces.source=f:pub, isbn=9780441013593 and jakarta.faces.partial.ajax=true, and the bean recorded
f:pub isbn=9780441013593 phase=INVOKE_APPLICATION (the phase prints as INVOKE_APPLICATION 5 on Mojarra and
INVOKE_APPLICATION(5) on MyFaces). The link opens the publisher’s page in a new tab (target="_blank") and the
background request is not cancelled.
Points that the example shows:
-
The event name.
context.getEventName()is the behavior event name of the holder, so the script does not hard-codeclick. For a button or link with the default event it isaction, for an inputvalueChange(observed). -
The source.
faces.ajax.request(this, event, ...)sendsjakarta.faces.sourceitself.getSourceId()of the context wasnullfor every standard renderer probed on both implementations, so the script cannot rely on it. -
Decoding. The renderer’s
decoderan only for the request that came from this link, because the component’s renderer comparesjakarta.faces.sourcewith its client id before it callsdecode(read in Mojarra’sHtmlBasicRenderer). -
Untrusted input. The
isbnread indecodeis whatever the browser sent. The event carries it, and the listener must treat it as data from an attacker: validate it, and do not write it to a log line or a page without escaping.TrackerBeanabove only stores it, which is enough for a demonstration and not enough for production. -
The component renders the handler.
h:commandLinkandh:commandButtonchain the script with their own submit, so that a tracking call on a button still submits the form (observed in Chrome on both implementations: an Ajax POST and then a normal form POST). The tracker is therefore attached to anh:outputLink, which has no submit of its own. -
Parameters.
context.getParameters()held thef:paramchildren of a command button and link (observedp=1). Forh:outputLink,h:link,h:button, inputs and the other holders, Mojarra passed an empty collection and MyFaces passednull, which the Javadoc says never happens: a script that iterates the collection must test fornull(a first version ofTrackBehaviorRendererthat did not raised aNullPointerExceptionat render time on MyFaces, so the renderer below reads no parameters). -
faces.js. The script callsfaces.ajax.request, which exists only if the page loads the Faces script. The standard components that need it add it themselves, a bare custom component does not (see the pill component).
The Behavior Context
ClientBehaviorContext is the argument of getScript. It is only valid during that call, and the Javadoc forbids
keeping a reference. The standard renderers build it with createClientBehaviorContext(context, component, behaviorEventName,
null, params) (read in Mojarra’s RenderKitUtils); a component that renders behaviors itself builds one the same way, as
the pill component below does. Its properties, as seen by a probe behavior that printed them into the script:
| Property | Observed value |
|---|---|
|
The behavior event name: |
|
The holder: |
|
|
|
The |
ClientBehaviorHint.SUBMITTING tells a renderer that the behavior posts back, so the renderer may leave out the script it
would otherwise add for the same event. AjaxBehavior returns it. What the renderers did with it, observed with a behavior
that claimed to be submitting although it only asked for confirmation:
-
On a command button, Mojarra rendered
onclick="return confirm('...');event.preventDefault()"and MyFacesonclick="faces.util.chain(...); return false;". On MyFaces the form was never submitted, even after accepting the dialog. On Mojarra thereturncomes beforepreventDefault(), so accepting still submitted the form and cancelling did not. -
On a command link, both implementations dropped their submit script, and nothing was submitted after accepting.
The hint is a promise: a behavior that sets it must perform the postback itself. A behavior that does not post back must not set it.
A Component That Accepts Behaviors
The other half of the contract is a component that implements ClientBehaviorHolder. UIComponentBase supplies
addClientBehavior and getClientBehaviors; the component declares the event names, renders each event’s scripts and decodes
the behaviors. The component below is a small label, bc:pill, that exposes click and dblclick. It renders itself
(a renderer would be cleaner, see the page on custom components and renderers) to keep the example in one class:
package org.example.bookshelf.web;
import jakarta.faces.application.ResourceDependency;
import jakarta.faces.component.FacesComponent;
import jakarta.faces.component.UIOutput;
import jakarta.faces.component.behavior.ClientBehavior;
import jakarta.faces.component.behavior.ClientBehaviorContext;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.ResponseWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/** A small label component that exposes "click" and "dblclick" to client behaviors. */
@ResourceDependency(library = "jakarta.faces", name = "faces.js")
@FacesComponent(value = "bookshelf.Pill", createTag = true, namespace = "urn:bookshelf:components", tagName = "pill")
public class Pill extends UIOutput implements ClientBehaviorHolder {
private static final List<String> EVENTS = List.of("click", "dblclick");
public Pill() {
setRendererType(null);
}
@Override
public Collection<String> getEventNames() {
return EVENTS;
}
@Override
public String getDefaultEventName() {
return "click";
}
@Override
public void decode(FacesContext context) {
super.decode(context);
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String event = params.get(ClientBehaviorContext.BEHAVIOR_EVENT_PARAM_NAME);
String source = params.get(ClientBehaviorContext.BEHAVIOR_SOURCE_PARAM_NAME);
if (event != null && getClientId(context).equals(source)) {
for (ClientBehavior behavior : getClientBehaviors().getOrDefault(event, List.of())) {
behavior.decode(context, this);
}
}
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String clientId = getClientId(context);
writer.startElement("span", this);
writer.writeAttribute("id", clientId, "id");
writer.writeAttribute("class", "pill", null);
for (Map.Entry<String, List<ClientBehavior>> entry : getClientBehaviors().entrySet()) {
String event = entry.getKey();
ClientBehaviorContext behaviorContext = ClientBehaviorContext.createClientBehaviorContext(
context, this, event, clientId, null);
StringBuilder chain = new StringBuilder("faces.util.chain(this,event");
int scripts = 0;
for (ClientBehavior behavior : entry.getValue()) {
String script = behavior.getScript(behaviorContext);
if (script != null) {
chain.append(",'").append(JsStrings.escape(script)).append('\'');
scripts++;
}
}
if (scripts > 0) {
writer.writeAttribute("on" + event, chain.append(')').toString(), null);
}
}
Object value = getValue();
if (value != null) {
writer.writeText(value, "value");
}
}
@Override
public void encodeEnd(FacesContext context) throws IOException {
context.getResponseWriter().endElement("span");
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:bs="urn:bookshelf:behaviors"
xmlns:bc="urn:bookshelf:components">
<h:head>
<title>Pill</title>
</h:head>
<h:body>
<h:form id="shelf">
<bc:pill id="pick" value="Staff pick">
<bs:confirm message="Remove the staff pick label?"/>
<bs:track event="dblclick" isbn="9780441013593" listener="#{trackerBean.onTracked}"/>
</bc:pill>
</h:form>
</h:body>
</html>
What the component has to do, because no renderer does it for it:
-
Event names.
getEventNamesandgetDefaultEventNamegive the names that nested tags may use. Without a default event a tag with noeventattribute fails (see above). -
Render the scripts. For each event, create a
ClientBehaviorContext, ask every behavior for its script, skipnull, and chain the rest withfaces.util.chain(this, event, script, ...), which runs them in order and stops at the first one that returnsfalse(spec 14.6). The component passes its own client id as the source id here. -
Decode. Read
jakarta.faces.behavior.eventandjakarta.faces.source, and calldecodeon the behaviors of that event when the source is the component. This is the rule the standard renderers apply. -
Load
faces.js. Mojarra adds the script to a page only when a component that needs it is rendered. On a page with only the pill,faces.utilwas undefined: Chrome reported "faces is not defined" for each click and no confirm dialog appeared, while MyFaces had added the script anyway. The@ResourceDependencyannotation on the component fixes it; the ClientBehavior Javadoc asks for the same annotation on a behavior class whose script calls into another resource, andTrackBehaviorcarries it. Observed: with the annotation only on the behavior, a page with the pill and the tracker renderedfaces.js, and with the annotation only on the pill, a page with the pill andbs:confirmdid.
With the annotation, observed on both implementations in Chrome: a click on the pill showed the dialog "Remove the staff pick
label?"; a double click sent the tracking request and the bean recorded isbn=9780441013593 (a double click also fires
click twice, so the dialog appeared twice).
Registering Behaviors and Renderers
Both pieces can be registered by annotation or in faces-config.xml; the two ways are equivalent and work on both
implementations (observed with the XML form below, a behavior and its renderer, used from a tag):
| What | Annotation | faces-config.xml |
|---|---|---|
Behavior |
|
|
Client behavior renderer |
|
|
<?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">
<behavior>
<behavior-id>bookshelf.greet</behavior-id>
<behavior-class>org.example.bookshelf.web.XmlGreetBehavior</behavior-class>
</behavior>
<render-kit>
<client-behavior-renderer>
<client-behavior-renderer-type>bookshelf.greet</client-behavior-renderer-type>
<client-behavior-renderer-class>org.example.bookshelf.web.XmlGreetRenderer</client-behavior-renderer-class>
</client-behavior-renderer>
</render-kit>
</faces-config>
package org.example.bookshelf.web;
import jakarta.faces.component.behavior.ClientBehaviorBase;
/** Registered in faces-config.xml instead of with an annotation. */
public class XmlGreetBehavior extends ClientBehaviorBase {
@Override
public String getRendererType() {
return "bookshelf.greet";
}
}
package org.example.bookshelf.web;
import jakarta.faces.component.behavior.ClientBehavior;
import jakarta.faces.component.behavior.ClientBehaviorContext;
import jakarta.faces.render.ClientBehaviorRenderer;
public class XmlGreetRenderer extends ClientBehaviorRenderer {
@Override
public String getScript(ClientBehaviorContext context, ClientBehavior behavior) {
return "alert('hello from " + context.getEventName() + "')";
}
}
The registered ids and types are plain strings, and a mismatch is silent. Observed on both implementations: a behavior whose
getRendererType() named a renderer that did not exist rendered no script and logged nothing, even in the Development
project stage. A tag whose behavior-id is not registered fails with an error on both implementations (see the attaching section).
The specification’s text for renderer registration by annotation (section 8.3.1) spells the annotation
@FacesClientBehaviorRenderer, which does not exist. The annotation in the API is jakarta.faces.render.FacesBehaviorRenderer,
and that is the one that worked.
Managed behaviors. @FacesBehavior(value = "id", managed = true) makes the behavior a CDI bean, so it can use @Inject
(spec 5.6.1 lists it with managed converters and validators). The class needs a bean-defining annotation such as @Dependent when
beans.xml uses bean-discovery-mode="annotated", as for the other managed Faces artifacts. Observed with a ClientBehaviorBase
that read an injected bean in getScript: MyFaces rendered the expected script ("Delete one of 3 reviews?"), but Mojarra 4.1.17
rendered the button with no script at all and no message. Mojarra’s CDI wrapper for managed behaviors
(com.sun.faces.cdi.CdiBehavior) implements only Behavior, not ClientBehavior, and the tag handler adds only
ClientBehavior instances to the holder, which explains it. A managed client behavior is therefore not portable today; keep the behavior unmanaged and give it a
ValueExpression that resolves the bean, as bs:confirmDynamic does.
Behaviors in Other Libraries and in Composite Components
f:ajax is the behavior everyone uses. PrimeFaces documents p:confirm as "a behavior element used to integrate with
global confirm dialog/popup": it is nested in a p:commandButton or p:commandLink and works with p:confirmDialog,
and its message can also be a facet of the parent. The PrimeFaces documentation warns that escape="false" on the message
exposes an XSS vector unless the content is trusted. Use the library’s behavior where you use the library. Write your own
when you need something the library does not cover, or when the page has no component library.
A composite component can pass a behavior on to one of its inner components with cc:clientBehavior in the interface, which
is how a composite can accept f:ajax from its caller. The page on composite components covers it.
Mojarra and MyFaces
Everything below was observed in the runs described at the top of the page:
| Topic | Mojarra 4.1.17 | MyFaces 4.1.4 |
|---|---|---|
Script of one behavior on a command button |
|
|
Two script behaviors (or a user |
|
|
Two script behaviors on a command link |
Cancel stops everything |
Cancel stops everything |
Script behavior plus |
|
|
|
|
|
|
|
none: error "eventName could not be defined" |
|
empty collection |
|
|
no script rendered (the CDI wrapper is not a |
works |
Faces script on a page with a bare custom holder |
not added: |
added |
Render id of the enclosing table from a button in one of its rows |
resolved to the row ( |
resolved to the table |
Phase text of |
|
|
The two rows about the chain matter most in practice: the order and the result of the scripts differ, so test cancel and accept on both implementations and in a real browser if you support both.
What Changed in Which Version
-
2.0: the component behavior model:
Behavior,ClientBehavior,ClientBehaviorHolder,ClientBehaviorBase,ClientBehaviorContext,ClientBehaviorHint,ClientBehaviorRenderer,@FacesBehavior,@FacesBehaviorRendererandAjaxBehavior(allSince: 2.0in the API documentation). -
2.3: the constants
ClientBehaviorContext.BEHAVIOR_EVENT_PARAM_NAMEandBEHAVIOR_SOURCE_PARAM_NAMEare added (Since: 2.3); the CDI section of the specification lists@FacesBehavior(managed = true)among the objects that must support@Inject. -
3.0: the packages move from
javax.facestojakarta.faces, and the reserved request parameter names carry the new prefix (jakarta.faces.behavior.event,jakarta.faces.source). -
4.0:
FacesBehavior.Literalis added for inline use of the qualifier (Since: 4.0); the script resource isfaces.jsinstead ofjsf.js. -
4.1: full state saving (
jakarta.faces.PARTIAL_STATE_SAVING=false) is deprecated for removal (StateManagerJavadoc); the behavior API itself is unchanged. -
5.0 (upcoming): the milestone releases remove deprecated APIs; this page was not checked against them and nothing here should be read as describing 5.0.
Where the Books Are Outdated
The oldest books in the bibliography (JavaServer Faces in Action by Mann, Mastering JavaServer Faces by Dudney and others, and
Pro JSF and Ajax by Jacobi and Fallows, all from 2004 to 2006) were written before JSF 2.0, so they cannot describe the behavior
model. In JSF 1.x client-side logic reached a component through its renderer (or a JSP tag that emitted the script), through
hand-written XMLHttpRequest code, or through the Ajax libraries of the time; a behavior attaches the same logic from the
page, without touching the component or its renderer. The books that can cover behaviors, Burns and Schalk’s reference and
Leonard’s cookbook for JSF 2.0, use the javax.faces packages, so every type on this page needs the jakarta.faces name
and the Jakarta namespaces. The Jacobi and Fallows book treats Ajax-enabled components through renderers, and the XUL and HTC
render kits it uses are gone. The PrimeFaces books describe component libraries that have moved on; for the confirmation
dialog, PrimeFaces 16 documents the p:confirm behavior described above (not run), so read the library’s current documentation
rather than the book. Everything on this page was checked on 4.1.
Pitfalls
-
Pasting text into a JavaScript string. Escape every value that reaches a script (
JsStringsabove). A title, a user name or a message from a resource bundle can contain a quote. The rendered handler is an inline attribute (observed on both implementations); a strict Content-Security-Policy that forbids inline handlers affects every behavior and was not tested here. -
A confirm dialog as authorization. It only asks the user. The server must check permissions for the action regardless.
-
Two script behaviors on one command button on Mojarra. Cancelling does not stop the submit. Use one behavior, or an
h:commandLink, orf:ajax(which always prevents the default submit), or test on the implementation you deploy. -
EL with a loop variable in a plain property. It is evaluated once at build time. Take a
ValueExpressionand a handler that passes it through. -
Properties without state. They disappear under full state saving, and the failure is a missing script, not an error.
-
No
eventonh:outputLinkorh:link. Writeevent="click": Mojarra rendersonaction, MyFaces fails. -
The
SUBMITTINGhint without a postback. The button stops submitting. -
A renderer type that does not match. No script, no message.
-
Null context values.
getParameters()can benullon MyFaces, andgetSourceId()isnullon both. -
No
faces.json pages that use only your component (Mojarra). Add@ResourceDependency. -
Trusting
decodeinput. Everything in the request is chosen by the client, including the behavior event and the source. A request without a valid view state is not decoded at all (observed), but that is not input validation. -
A session-scoped bean behind
binding. One behavior instance would be shared by every view of the session. Keep it request scoped. -
A managed client behavior on Mojarra. The behavior is silently ignored.