Composite Components
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Jakarta Faces 4.1 (API jakarta.faces-api 4.1.2, part of Jakarta EE 11), Eclipse Mojarra 4.1.17
and Apache MyFaces Core 4.1.4, with Java 17 or later (the Java in the examples was compiled with --release 21). Composite
components have existed since JSF 2.0, so everything below also applies to Faces 4.0, with the namespace and deprecation
differences listed in What Changed per Version; only 4.1 was run. The examples were deployed as an exploded Bookshelf WAR on Apache Tomcat
11.0.26 with Weld 6.0.4.Final (and Hibernate Validator 9.1.4 where Bean Validation is mentioned), once with Mojarra and once with
MyFaces, and driven with scripted HTTP requests; every message and rendered fragment quoted on this page comes from those runs
(checked on 2026-10-08). Where the two implementations disagree, Mojarra and MyFaces Compared collects the differences. Faces 5.0 is
still a milestone release and nothing here describes it as current behaviour. Claims that were not run are marked as such.
A composite component is a real Faces component, with attributes, a client id, converters and validators, listeners, Ajax and a place in the component tree, that is written as one XHTML file and no Java. The specification states the goal plainly: to let developers write real, reusable Faces UI components without any Java code or configuration XML. It is the right tool whenever a group of existing components keeps reappearing together, such as a label, an input and a message, or a star rating built from a radio group, and it is the first thing to reach for before writing a custom component with its own renderer.
What a Composite Component Is
A composite component is any Facelets markup file that lives inside a resource library. If the file starRating.xhtml is in
the library bookshelf, a page declares the namespace xmlns:bs="jakarta.faces.composite/bookshelf" and writes
<bs:starRating .../>. The library name is whatever follows jakarta.faces.composite/, the tag name is the file name
without the extension, and no tag library descriptor is needed. Inside the file two sections describe the component:
cc:interface is the contract the page author sees, and cc:implementation is the markup that is built when the tag is used.
Faces offers three ways to package a reusable piece of a view. They differ in how much they build and how much Java they need:
| Composite component | Facelets tag file | Custom component | |
|---|---|---|---|
What it is |
A real |
A template that is inlined into the including view; no component of its own |
A Java class extending |
Java needed |
None; optional backing component |
None; optional tag handlers |
Always |
Configuration |
None (a resource library); optional tag library to give it a custom namespace |
A |
Annotations or |
Own client id, naming container, state |
Yes |
No |
Yes |
Listeners, converters, validators, Ajax from the page author |
Yes, through attached objects that you expose |
Only on the components inside, which the page author cannot reach |
Yes, through the behavioural interfaces it implements |
Typical use |
"A labelled field", "a star rating", "a panel with a header facet" |
Shared fragments with parameters, repeated markup |
A new widget with its own HTML and JavaScript, such as a chart |
The page on Facelets tag files and tag libraries and the page on custom components and renderers describe the other two; this page stays with composites. The architecture page explains the component tree they plug into (the component tree).
A First Composite Component: the Book Card
The Bookshelf catalogue shows each book as a small card. The markup for one card is the same everywhere it appears, so it is a
composite component. The file is src/main/webapp/resources/bookshelf/bookCard.xhtml:
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:h="jakarta.faces.html"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:attribute name="book" type="org.example.bookshelf.model.Book" required="true"/>
<cc:attribute name="showYear" type="java.lang.Boolean" default="true"/>
</cc:interface>
<cc:implementation>
<article id="#{cc.clientId}" class="book-card">
<h3><h:outputText value="#{cc.attrs.book.title}"/></h3>
<p>by <h:outputText value="#{cc.attrs.book.author}"/>
<h:outputText value=" (#{cc.attrs.book.year})" rendered="#{cc.attrs.showYear}"/></p>
</article>
</cc:implementation>
</ui:component>
-
cc:interfacedeclares two attributes.bookis required and is typed with a class name;showYearis optional, typedjava.lang.Boolean, and defaults totrue. -
cc:implementationis ordinary Facelets markup. The attributes are read through theccimplicit object:#{cc.attrs.book.title}is thetitleproperty of whatever the page author passed asbook. -
#{cc.clientId}is the client id of the composite itself. This matters more than it looks: a composite component renders no markup of its own. Its renderer only renders its children, so an HTML wrapper that carries the component’s id is the author’s job, and the line<article id="#{cc.clientId}" ...>is how that is done.
A page uses it like any other component:
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:ui="jakarta.faces.facelets"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Catalogue</title>
</h:head>
<h:body>
<h:form id="shelf">
<ui:repeat value="#{catalogue.books}" var="book">
<bs:bookCard id="card" book="#{book}"/>
</ui:repeat>
<bs:bookCard id="plain" book="#{catalogue.books[0]}" showYear="false"/>
</h:form>
</h:body>
</html>
The CatalogueBean is the request-scoped bean of the data tables page, reduced to the one method this page needs; it uses the
Book record and BookService introduced there (see the data used on that page):
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;
import org.example.bookshelf.model.Book;
import org.example.bookshelf.service.BookService;
@Named("catalogue")
@RequestScoped
public class CatalogueBean {
@Inject
private BookService service;
public List<Book> getBooks() {
return service.findAll();
}
}
The card in the repeat rendered, on Mojarra, as <article id="shelf:j_idt5:0:card" class="book-card"> for the first book, ...:1:card
for the second and so on, and as <article id="shelf:plain" class="book-card"> for the card outside the repeat (MyFaces
produced the same shape with j_id_7 in place of j_idt5). Because the composite is a naming container, its id is
prefixed by the form and, inside ui:repeat, by the row index, exactly as for any other component.
Where the File Lives: Resource Libraries and Namespaces
The specification (sections 3.6.1.3 and 10.3.3.1) allows two ways to tell Facelets where composite components are.
The namespace form. A namespace that starts with jakarta.faces.composite/ is taken to mean "a composite library"; the rest
of the string is the library name. The runtime looks for a resource library of that name, so the file is found at:
| Location | Used for |
|---|---|
|
Components of the web application (observed: |
|
Reusable component libraries shipped as a JAR (observed with a JAR holding |
The library name must not contain a / or characters that are illegal in a library name. If it does, the specification says
that in the Development project stage an informative message is placed in the page and logged, and in other stages it is
only logged. Library versions and locale-specific variants follow the resource handling rules (a sibling page covers them); a
composite is found the same way as any other resource.
The tag library form. A Facelets tag library descriptor may contain a composite-library-name element. The namespace in
that same descriptor can then be any URI, which is useful for a stable, company-owned name that survives moving the files:
<?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>https://bookshelf.example.org/ui</namespace>
<composite-library-name>bookshelf</composite-library-name>
</facelet-taglib>
The descriptor is registered with the context parameter jakarta.faces.FACELETS_LIBRARIES (the page on tag files and tag
libraries covers the other ways a descriptor is found):
<context-param>
<param-name>jakarta.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/bookshelf.taglib.xml</param-value>
</context-param>
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:b="https://bookshelf.example.org/ui">
<h:head>
<title>Taglib</title>
</h:head>
<h:body>
<h:form id="review">
<b:starRating id="rating" value="#{reviewBean.rating}"/>
</h:form>
</h:body>
</html>
Source: Jakarta Faces 4.1 specification, 10.3.3.1 Declaring a composite component library for use in a Facelet page and facelet-taglib 4.1 schema (composite-library-name)
Both forms worked on Mojarra and on MyFaces (the page rendered the same rating group with jakarta.faces.composite/bookshelf and
with https://bookshelf.example.org/ui). The pre-Jakarta namespaces were still accepted by both: a page that declared
xmlns:b="http://xmlns.jcp.org/jsf/composite/bookshelf" rendered the same component. Do not rely on that. The old namespaces
are on the list of planned removals for Faces 5.0, and the page on Facelets and XHTML views has a script that migrates them
(the old namespaces).
What Faces Builds
The non-normative walk-through in section 3.6.1.5 of the specification describes the sequence, and what I observed is consistent with it. When the view is built (in Restore View of a postback, and for an initial request just before it is rendered; see the lifecycle page):
-
The runtime sees a namespace beginning with
jakarta.faces.composite/, takes the rest as the library name and checks that the library exists (ResourceHandler.libraryExists()). -
For the tag
<bs:starRating>it callsApplication.createComponent(FacesContext, Resource). That method asks the view declaration language for the composite metadata (ViewDeclarationLanguage.getComponentMetadata(), aBeanInfobuilt from thecc:interface), then decides which Java class to instantiate, in this order: thecomponentTypedeclared oncc:interface; a script component for the resource; a Java class called<library>.<file name without extension>; and otherwise the default, aUINamingContainer(component typejakarta.faces.NamingContainer). The renderer type of the new component is set tojakarta.faces.Composite. -
The children of the file’s
cc:implementationare put in a facet of the new component, the facet named by the constantUIComponent.COMPOSITE_FACET_NAME, under aUIPanelwith the rendererjakarta.faces.Group. -
After the children of the tag in the using page have been processed, the runtime retargets the attached objects (
f:validator for="..."and the others, see Attached Objects: Letting the Page Author Reach Inner Components) to the inner components thatcc:interfaceexposed, and the method expressions (see Method Attributes and Retargeting) to the inner components'action,actionListener,validatorandvalueChangeListenerproperties.
The resulting part of the component tree for one composite tag is this (read from the Development-stage error page that
Mojarra prints, which dumps the tree, and confirmed by the cc probe in The cc Implicit Object):
UINamingContainer id="p1" <- the composite, renderer jakarta.faces.Composite
facet jakarta.faces.component.COMPOSITE_FACET_NAME
UIPanel <- renderer jakarta.faces.Group
(the components of cc:implementation)
Source: Jakarta Faces 4.1 specification, 3.6 Composite User Interface Components and Jakarta Faces 4.1 API: Application.createComponent(FacesContext, Resource)
Where this sits in the request processing lifecycle follows from that: the composite is created while the view is built, so the
cc:interface and the tag handlers in cc:implementation (c:if, ui:include, the static structure) are evaluated once per
build. The expressions in attribute values are evaluated later, lazily, in the phase that needs them: #{cc.attrs.label}
while rendering, #{reviewBean.rating} of an inner input while decoding, validating (Process Validations), updating the
model (Update Model Values) and rendering. Inner components take part in the lifecycle exactly like components written
directly in the page; the composite does not add a phase.
The cc Implicit Object
Inside a composite’s cc:implementation, cc is the current composite component: the UIComponent created for the tag. The
specification (change C034, accepted from the JSF 2.0 change log) says that cc is the composite whose markup file contains
the expression, relative to the composite markup page in which it appears. Outside a composite it is empty, which the expression language
page noted as well. In Java the same object is returned by UIComponent.getCurrentCompositeComponent(FacesContext); the static
helpers UIComponent.getCompositeComponentParent(UIComponent) (the nearest composite above a component) and
UIComponent.isCompositeComponent(UIComponent) complement it.
The commonly used properties, read in a probe composite (the attribute behaviour in the right-hand column is the subject of the next section):
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:attribute name="req" required="true"/>
<cc:attribute name="def" default="D"/>
<cc:attribute name="typed" type="java.lang.Integer" default="7"/>
<cc:attribute name="flag" type="java.lang.Boolean" default="true"/>
</cc:interface>
<cc:implementation>
<p id="#{cc.clientId}-out">
req=[#{cc.attrs.req}] def=[#{cc.attrs.def}] typed=[#{cc.attrs.typed}] flag=[#{cc.attrs.flag}]
typed+1=[#{cc.attrs.typed + 1}]
extra=[#{cc.attrs.extra}] id=[#{cc.id}] clientId=[#{cc.clientId}] family=[#{cc.family}]
rendererType=[#{cc.rendererType}] facets=[#{cc.facets.keySet()}]
</p>
</cc:implementation>
</ui:component>
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Probe</title>
</h:head>
<h:body>
<h:form id="f">
<bs:probe id="p1" req="x"/>
<bs:probe id="p2" req="x" def="given" typed="41" flag="false" extra="E"/>
<bs:probe id="p3" req="#{param.n}" typed="#{param.n}" def="#{param.d}"/>
</h:form>
</h:body>
</html>
Source: Jakarta Faces 4.1 specification, 3.6.1.3 How does one make a composite component? and Jakarta Faces 4.1 API: UIComponent
Rendered (the same on Mojarra and MyFaces, ?n=5&d=Z appended for the third card in the second line):
| Expression | Value observed |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mojarra: |
cc:interface and cc:implementation
A composite file has at most one cc:interface and at most one cc:implementation, in that order.
-
cc:interfacedeclares the usage contract: attributes, facets and the inner components whose features are exposed (Attached Objects: Letting the Page Author Reach Inner Components). Its own attributes arecomponentType(the component type of the root, which must be in the familyjakarta.faces.NamingContainer; see Backing Components) and the tooling metadataname,displayName,shortDescription,expert,hiddenandpreferred.nameis advisory only: the real name is the file name. The specification adds thatdisplayName,shortDescription,expert,hiddenandpreferredare exposed to tools only when the project stage isDevelopment. -
cc:implementationis the markup that becomes the children of the composite. Any valid Facelets markup may appear in it, including the templating tags (ui:include,ui:repeat,ui:insert). -
If a
cc:interfaceis present, acc:implementationmust accompany it. The VDL documentation says that without acc:interfacethe contract is inferred.
The inferred contract has a portability trap. A file that has only cc:implementation, with no cc:interface at all, was
rejected by Mojarra 4.1.17 with /resources/bookshelf/noif.xhtml @2,22 <cc:implementation> Unable to find interface for
implementation. and rendered by MyFaces 4.1.4. Always write the interface, even an empty one: <cc:interface/> followed by
the implementation rendered on both.
cc:attribute
cc:attribute declares an attribute that the page author may give to the tag. There can be zero or many in cc:interface.
Their name is what the page author types; their other attributes are:
| Attribute | Meaning |
|---|---|
|
The attribute name on the tag. The values |
|
The class the value must be assignable to, as a fully qualified name. Default |
|
The value to use when the page author does not supply one. |
|
|
|
Declares the attribute a |
|
With a |
|
The attribute of the inner component that receives the method expression, when it differs from |
|
Tooling metadata, set on the composite’s |
The VDL adds that cc:attribute may be nested in another cc:attribute to describe nested properties. It also lists properties
that must not be exposed as attributes because the markup-to-property mapping cannot set them: binding, id, inView,
parent, rendered, rendererType and transient.
The probe above shows how the attributes behave. The runs on both implementations agreed:
-
Required.
<bs:probe id="p"/>withoutreqfailed while the view was built, not while rendering. Mojarra:/probe-missing.xhtml @8,21 <bs:probe> The following attribute(s) are required, but no values have been supplied for them: req.MyFaces:/probe-missing.xhtml at line 8 and column 21 <bs:probe> Attribute 'req' is required. Only an attribute that is missing triggers this;req="#{param.n}"with no parameter passed the check and rendered an empty value. -
Default. With the attribute absent,
typedread7anddefreadD. The default was also used when the supplied expression evaluated tonull:def="#{param.d}"with nodparameter renderedD, andtyped="#{param.n}"with nonrendered7. Withn=5andd=Zthe supplied values won. -
Type. With
type="java.lang.Integer",typed="#{param.n}"andn=5read5and#{cc.attrs.typed + 1}gave6;typed="41"gave41. An empty parameter (n=) was coerced to0by Expression Language. A value that cannot be coerced (n=abc) was an error page on both (Cannot convert [abc] of type [class java.lang.String] to [class java.lang.Integer]), not a validation message:typeis an Expression Language coercion, not a Faces validator. -
Undeclared attributes.
extra="E"was not declared, yet#{cc.attrs.extra}readE. Do not rely on that; the contract is what tools and later readers see. -
Attributes as value expressions.
value="#{reviewBean.rating}"passed through to an inner input, as in the star rating below, is writable: the inner component’s postback value was stored in the bean. A literal (value="3"on the star rating) has no property behind it: Mojarra rendered the 3 as selected, and on both implementations a postback showed the submitted choice again (kept in the component) while no bean property was written.
The Bookshelf Star Rating, Version 1: No Java
The review form of Bookshelf needs a way to pick a rating. A set of radio buttons inside a labelled group is accessible
without any script, works with a keyboard, and has the right semantics for a screen reader, so the first version is a
h:selectOneRadio inside a fieldset. Version 1 uses no Java at all:
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:f="jakarta.faces.core"
xmlns:h="jakarta.faces.html"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:attribute name="value" type="java.lang.Integer" required="true"/>
<cc:attribute name="label" type="java.lang.String" default="Rating"/>
<cc:attribute name="required" type="java.lang.Boolean" default="false"/>
<cc:attribute name="readonly" type="java.lang.Boolean" default="false"/>
<cc:editableValueHolder name="stars"/>
<cc:clientBehavior name="change" event="change" targets="stars" default="true"/>
</cc:interface>
<cc:implementation>
<h:outputStylesheet library="bookshelf" name="starRating.css" target="head"/>
<fieldset id="#{cc.clientId}" class="star-rating">
<legend>#{cc.attrs.label}</legend>
<h:selectOneRadio id="stars" value="#{cc.attrs.value}" label="#{cc.attrs.label}"
required="#{cc.attrs.required}" disabled="#{cc.attrs.readonly}"
layout="lineDirection">
<f:selectItem itemValue="1" itemLabel="1 star"/>
<f:selectItem itemValue="2" itemLabel="2 stars"/>
<f:selectItem itemValue="3" itemLabel="3 stars"/>
<f:selectItem itemValue="4" itemLabel="4 stars"/>
<f:selectItem itemValue="5" itemLabel="5 stars"/>
</h:selectOneRadio>
<h:message for="stars"/>
</fieldset>
</cc:implementation>
</ui:component>
-
valueis the rating (anInteger),labelnames the group, andrequiredandreadonlyare switches. They are passed straight to the radio group, so the standardrequiredbehaviour applies. -
cc:editableValueHolder name="stars"exposes the radio group, whoseidisstars, as a target for converters, validators and value-change listeners.cc:clientBehaviordoes the same for Ajax (Attached Objects: Letting the Page Author Reach Inner Components). -
The
labelof the inner input is the composite’s label, so a validation message names the component the reader sees ("Your rating: Validation Error: Value is required.") rather than the client id. -
h:outputStylesheetinsidecc:implementationregisters a stylesheet from the same library, once per page however many ratings are on it. Both implementations put the single<link rel="stylesheet" ...?ln=bookshelf>in the head.
.star-rating { border: 0; margin: 0 0 1em; padding: 0; }
.star-rating legend { font-weight: bold; padding: 0; }
.star-rating table { border-collapse: collapse; }
The review page uses the component, attaches a range validator to the radio group inside it, and an Ajax update:
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Review</title>
</h:head>
<h:body>
<h:form id="review">
<h:messages globalOnly="true"/>
<bs:starRating id="rating" label="Your rating" value="#{reviewBean.rating}" required="true">
<f:validateLongRange for="stars" minimum="2"/>
<f:ajax render="summary"/>
</bs:starRating>
<h:outputLabel for="comment" value="Comment"/>
<h:inputTextarea id="comment" value="#{reviewBean.comment}"/>
<h:outputText id="summary" value="You rated this #{reviewBean.rating} out of 5."
rendered="#{reviewBean.rating != null}"/>
<h:commandButton id="save" value="Save review" action="#{reviewBean.save}"/>
</h:form>
</h:body>
</html>
package org.example.bookshelf.web;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.ActionEvent;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import java.io.Serializable;
@Named
@ViewScoped
public class ReviewBean implements Serializable {
@Min(value = 1, message = "Choose at least one star.")
@Max(value = 5, message = "Five stars is the maximum.")
private Integer rating;
@Min(1)
@Max(3)
private Integer difficulty;
private Integer lastRated;
private String clicked;
@Size(max = 500)
private String comment;
@Inject
private transient FacesContext facesContext;
public String save() {
facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,
"Thanks, your review was saved.", null));
return null;
}
public void saveClicked(ActionEvent event) {
clicked = event.getComponent().getClientId();
}
public String cancel() {
rating = null;
comment = null;
return null;
}
/** Called by the star rating component whenever the reader picks another number of stars. */
public String rated(Integer stars) {
lastRated = stars;
return null;
}
public Integer getDifficulty() { return difficulty; }
public void setDifficulty(Integer difficulty) { this.difficulty = difficulty; }
public String getClicked() { return clicked; }
public Integer getLastRated() { return lastRated; }
public Integer getRating() { return rating; }
public void setRating(Integer rating) { this.rating = rating; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
}
Source: Jakarta EE Tutorial: The compositecomponentexample Example Application and Jakarta Faces 4.1 VDL: cc:editableValueHolder
The bean already carries the Bean Validation constraints, the Integer-typed rating, difficulty and the rated method
that later versions use. Driven with HTTP (each request is a postback of the view with the field values named below; both
implementations gave the same outcome, apart from the message prefix noted in Mojarra and MyFaces Compared):
| Posted | Result |
|---|---|
|
|
|
Model updated, |
nothing for the rating |
|
Ajax |
Only the |
The Ajax row deserves a note. The render="summary" is evaluated where the f:ajax is written, which is the using page, so the
id is resolved from the form and the HTML became mojarra.ab(this,event,'change','review:rating:stars','review:summary')
(MyFaces: myfaces.ab('review:rating:stars:0',event,'change','review:rating:stars','review:summary',{})).
Attached Objects: Letting the Page Author Reach Inner Components
A page author using <bs:starRating> cannot name the radio group inside it: its client id is an implementation detail. Yet
the author may want a validator, a converter, a listener, or Ajax on it. Faces solves this with attached objects and
attached object targets: the composite author names an inner component in cc:interface, and the page author attaches the
object to that name with the for attribute. When the tag has been processed, Faces moves each attached object onto the
inner component.
| Interface tag | Exposes an implementation of | Page author can attach |
|---|---|---|
|
|
|
|
|
The above plus |
|
|
|
|
|
|
Each of the four takes a required name, which the page author’s for refers to, and an optional targets, a space-separated
list of ids relative to the composite. Without targets, name is also the id of the inner component. With targets, one
name can address several inner components at once (the valueHolder documentation uses two inputs, userid and
password, behind the name inputs). cc:clientBehavior adds event, the event name passed to the target’s
addClientBehavior(), and default: if true, the page author may omit event on f:ajax, as action is the default
event of a command link. The VDL notes that only the first default is used when several are declared.
The tags with a for attribute are listed in their VDL pages: all the converters and validators above, f:validator,
f:converter, f:valueChangeListener, f:actionListener and f:setPropertyActionListener. f:ajax has no for; it attaches
to the composite through cc:clientBehavior, and f:validateWholeBean has none either.
In the star rating, cc:editableValueHolder name="stars" is what allowed <f:validateLongRange for="stars" minimum="2"/> in the
review page, and cc:clientBehavior name="change" event="change" targets="stars" default="true" is what allowed the bare
<f:ajax render="summary"/>. A composite that exposes an action source follows below, in Method Attributes and Retargeting.
cc:valueHolder is the right tag when only a converter makes sense, as for an output-only amount. The composite below shows a
number in an h:outputText and lets the page author choose how it is formatted:
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:h="jakarta.faces.html"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:attribute name="value" required="true"/>
<cc:valueHolder name="amount"/>
</cc:interface>
<cc:implementation>
<span id="#{cc.clientId}" class="amount"><h:outputText id="amount" value="#{cc.attrs.value}"/></span>
</cc:implementation>
</ui:component>
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Fines</title>
</h:head>
<h:body>
<p>Late fee: <bs:amount id="fee" value="#{3.5}">
<f:convertNumber for="amount" type="currency" currencyCode="EUR" locale="en_US"/>
</bs:amount></p>
<p>Without a converter: <bs:amount id="plain" value="#{3.5}"/></p>
</h:body>
</html>
The first amount rendered €3.50 and the second 3.5 on both implementations. The converters page of this section explains
f:convertNumber itself (number conversion).
Three pitfalls of attached objects, all observed:
-
A misspelled
foris silent.<f:validateLongRange for="starz" minimum="4"/>was accepted on both implementations and the validator was never applied (a value of 1 produced only the bean’s constraint message). -
f:ajaxneeds acc:clientBehavior. On a composite without one, Mojarra stopped the page with<f:ajax> Error: enclosing composite component does not support event null, and so it did when the composite had acc:clientBehaviorwithoutdefault="true"and thef:ajaxomittedevent. MyFaces rendered the page without error and, by inspection of the output, without Ajax on the component. -
fornames thenameof the target, not the id of an inner component. They are the same only whentargetsis absent.
Method Attributes and Retargeting
A button inside the composite should call whatever the page author passes as its action. cc:attribute with a
method-signature declares such a method attribute, and the four names action, actionListener, validator and
valueChangeListener are retargeted automatically (the VDL refers to retargetMethodExpressions()): the method
expression the page author wrote is applied to the inner component named by targets, and the method-signature is derived,
so any given value is ignored. Any other name with a method-signature just stores a MethodExpression in the component’s
attributes for a backing component to call (see Backing Components).
targetAttributeName lets the public name differ from the inner property. The VDL gives the example of a "submit" and a
"cancel" button, each retargeted to the action property of its own button. The Bookshelf review buttons do this:
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:h="jakarta.faces.html"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:attribute name="action" targets="save" method-signature="java.lang.String action()"/>
<cc:attribute name="actionListener" targets="save"/>
<cc:attribute name="cancelAction" targetAttributeName="action" targets="cancel"
method-signature="java.lang.String action()"/>
<cc:actionSource name="save"/>
</cc:interface>
<cc:implementation>
<h:commandButton id="save" value="Save review"/>
<h:commandButton id="cancel" value="Cancel" immediate="true"/>
</cc:implementation>
</ui:component>
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Buttons</title>
</h:head>
<h:body>
<h:form id="review">
<h:inputText id="comment" value="#{reviewBean.comment}" required="true"/>
<h:messages/>
<bs:reviewButtons id="buttons" action="#{reviewBean.save}" actionListener="#{reviewBean.saveClicked}" cancelAction="#{reviewBean.cancel}">
<f:actionListener for="save" binding="#{reviewAudit}"/>
</bs:reviewButtons>
<h:outputText id="state" value="clicked=#{reviewBean.clicked} comment=#{reviewBean.comment}"/>
</h:form>
</h:body>
</html>
ReviewAudit is a request-scoped bean that implements ActionListener; it is bound through binding and logs which button was
used:
package org.example.bookshelf.web;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.ActionEvent;
import jakarta.faces.event.ActionListener;
import jakarta.inject.Named;
import java.util.logging.Logger;
@Named("reviewAudit")
@RequestScoped
public class ReviewAudit implements ActionListener {
private static final Logger LOG = Logger.getLogger(ReviewAudit.class.getName());
@Override
public void processAction(ActionEvent event) throws AbortProcessingException {
LOG.info("AUDIT save clicked on " + event.getComponent().getClientId());
}
}
Posting the form (identical on both implementations):
| Posted | Result |
|---|---|
|
|
empty comment and |
|
empty comment and |
|
The button ids come from the composite: save and cancel are inside the naming container buttons, so a script or a test
posts review:buttons:save, not review:save.
Nesting and Naming Containers
Composites compose. The cc:implementation of one may use another, which makes it the using page for that other composite. For
an attached object to reach through both levels, the specification requires that the name of the target, the for of the
attached object and the id of the inner component match at every level of the nesting; it does not require support for
renaming a target on the way up. Here a composite row adds a label to the star rating and passes the exposed target through:
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:h="jakarta.faces.html"
xmlns:bs="jakarta.faces.composite/bookshelf"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:attribute name="value" type="java.lang.Integer" required="true"/>
<cc:attribute name="aspect" type="java.lang.String" required="true"/>
<cc:editableValueHolder name="stars"/>
</cc:interface>
<cc:implementation>
<bs:starRating id="stars" label="#{cc.attrs.aspect}" value="#{cc.attrs.value}"/>
</cc:implementation>
</ui:component>
The row’s id="stars" for the inner bs:starRating, the row’s own cc:editableValueHolder name="stars" and the star rating’s
name="stars" all agree, so the page author’s <f:validateLongRange for="stars"/> travels two levels.
The second rule concerns naming containers inside the implementation. A composite is itself a naming container, so ids inside it
do not clash with the page. But a h:form, a ui:repeat or another composite inside the implementation is a naming container
too, and a target inside it must be named by a client id relative to the top-level component. The specification’s own example
is targets="form:button". The Bookshelf quick-note composite wraps its own form:
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:h="jakarta.faces.html"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:actionSource name="send" targets="note:send"/>
<cc:editableValueHolder name="text" targets="note:text"/>
</cc:interface>
<cc:implementation>
<h:form id="note">
<h:inputText id="text" value="#{cc.attrs.value}"/>
<h:commandButton id="send" value="Send"/>
</h:form>
</cc:implementation>
</ui:component>
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Nested</title>
</h:head>
<h:body>
<h:form id="review">
<bs:ratingRow id="plot" aspect="Plot" value="#{reviewBean.rating}">
<f:validateLongRange for="stars" minimum="2"/>
</bs:ratingRow>
<h:commandButton id="save" value="Save" action="#{reviewBean.save}"/>
</h:form>
<bs:quickNote id="quick" value="#{reviewBean.comment}">
<f:validateLength for="text" maximum="5"/>
<f:actionListener for="send" binding="#{reviewAudit}"/>
</bs:quickNote>
</h:body>
</html>
Observed on Mojarra and MyFaces: with review:plot:stars:stars=1 the page showed Plot: Validation Error: Value is less than allowable minimum of '2' (the validator travelled through both composites), with =3 the review was saved, and the quick note
with toolongvalue showed quick:note:text: Validation Error: Length is greater than allowable maximum of '5' while the
listener bound to send ran. A target that is not a naming container failed differently: a first attempt that wrote
targets="sub:button" for a button inside an h:panelGroup id="sub" (not a naming container, so the client id is just button)
made Mojarra throw java.lang.IllegalArgumentException: sub while the view was built, whereas MyFaces rendered the page without
complaint. Use h:form, ui:repeat or a composite, or drop the prefix.
Children and Facets
The tag in the using page may contain content, and cc:implementation decides where it goes. The VDL defines four tags:
| Tag | Purpose |
|---|---|
|
Everything nested inside the composite tag in the using page, components and template text, is re-parented to this position. The normal use is one occurrence. Several occurrences may cause duplicate-id errors and the results are undefined. |
|
Declares a facet the page author may supply with |
|
Renders the named facet at this point of the output. The runtime inserts a component with renderer type
|
|
Takes the named facet of the composite and inserts it as a facet child of the component in which this tag is nested.
|
renderFacet writes the facet content in place; insertFacet hands it to an inner component that has its own facet slot for it,
such as the footer of h:panelGrid or the header of h:dataTable. The Bookshelf panel uses all of them:
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:f="jakarta.faces.core"
xmlns:h="jakarta.faces.html"
xmlns:cc="jakarta.faces.composite">
<cc:interface>
<cc:attribute name="title" type="java.lang.String" default="Untitled"/>
<cc:facet name="header"/>
<cc:facet name="footer" required="true"/>
</cc:interface>
<cc:implementation>
<section id="#{cc.clientId}" class="panel">
<h2>#{cc.attrs.title}</h2>
<div class="panel-header"><cc:renderFacet name="header"/></div>
<div class="panel-body"><cc:insertChildren/></div>
<h:panelGrid columns="1" styleClass="panel-grid">
<f:facet name="footer"><cc:insertFacet name="footer" required="true"/></f:facet>
<h:outputText value="Reviews are moderated."/>
</h:panelGrid>
</section>
</cc:implementation>
</ui:component>
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Panel</title>
</h:head>
<h:body>
<h:form id="shelf">
<bs:panel id="reviews" title="Reviews">
<f:facet name="header"><h:outputText value="Latest first"/></f:facet>
<p>No reviews yet.</p>
<h:outputText id="hint" value="Be the first."/>
<f:facet name="footer"><h:commandLink id="more" value="Write one"/></f:facet>
</bs:panel>
</h:form>
</h:body>
</html>
The rendered body (Mojarra; MyFaces produced the same elements without the line breaks) was:
<section id="shelf:reviews" class="panel">
<h2>Reviews</h2>
<div class="panel-header">Latest first</div>
<div class="panel-body">
<p>No reviews yet.</p><span id="shelf:reviews:hint">Be the first.</span></div><table class="panel-grid">
<tfoot>
<tr><td colspan="1">...<a id="shelf:reviews:more" href="#" ...>Write one</a></td></tr>
</tfoot>
<tbody>
<tr>
<td>Reviews are moderated.</td>
</tr>
</tbody>
</table>
</section>
Details worth knowing, all from the runs (Mojarra and MyFaces unless said otherwise):
-
The text child
<p>No reviews yet.</p>and the component childhintboth landed atcc:insertChildren, and the component kept its composite-prefixed idshelf:reviews:hint. -
A missing
requiredfacet is a build-time error: MojarraThe following facets(s) are required, but no facets have been supplied for them: footer., MyFacesThe following facets are required by the component: [footer]. A missing optional facet (header) rendered an empty element. A facet that was not declared (sidebar) was silently ignored. -
cc:insertFacetmust be nested in a component that renders that facet. Put directly in ah:panelGroup, whose renderer has no slot calledfooter, it rendered nothing on either implementation: the output was<div class="foot"></div>.
Backing Components
Most composites need no Java. When one does, for logic that depends on its own attributes, for a validator that must read them,
or for a property the markup cannot compute, the composite gets a backing component: a Java class extending
UINamingContainer (or any class that implements NamingContainer and is in the family jakarta.faces.NamingContainer), used as the composite
root. Faces looks for it in this order, as the javadoc of Application.createComponent(FacesContext, Resource) states: the
componentType on cc:interface; a script component; a class named after the library and file; the default
UINamingContainer.
The explicit way registers the class with @FacesComponent and names it in cc:interface:
@FacesComponent element |
Meaning |
|---|---|
|
The component type, the name used in |
|
When |
The 4.1 annotation has no other elements. The second way needs no annotation: a class named <library>.<file name> is found by
name. A library bookshelf and a file conv.xhtml give a class bookshelf.conv; the class name is lower-case because the file
name is, so this is an unusual style:
package bookshelf;
import jakarta.faces.component.UINamingContainer;
/** Found by name: library "bookshelf", resource conv.xhtml, so the class is bookshelf.conv. */
public class conv extends UINamingContainer {
public String getGreeting() {
return "created as " + getClass().getName();
}
}
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:cc="jakarta.faces.composite">
<cc:interface/>
<cc:implementation>
<span>#{cc.greeting}</span>
</cc:implementation>
</ui:component>
On both implementations a page with <bs:conv id="c"/> rendered created as bookshelf.conv. Prefer @FacesComponent with a
componentType: it survives a rename of the file and uses ordinary Java names.
Source: Jakarta Faces 4.1 API: FacesComponent and Jakarta Faces 4.1 API: Application.createComponent(FacesContext, Resource)
The Bookshelf Star Rating, Version 2: a Backing Component
Version 1 has five fixed stars and a validator that knows nothing about the number of stars. Version 2 adds a max attribute,
builds the items from it, validates against it, and tells a method of the page author’s when the reader picks a rating:
package org.example.bookshelf.web;
import jakarta.el.MethodExpression;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.component.FacesComponent;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UINamingContainer;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.ValueChangeEvent;
import jakarta.faces.model.SelectItem;
import jakarta.faces.validator.ValidatorException;
import java.util.ArrayList;
import java.util.List;
/** Backing component of the bs:starRating composite component. */
@FacesComponent("bookshelf.StarRating")
public class StarRating extends UINamingContainer {
/** The number of stars: the max attribute, 5 when it is absent. */
public int getStarCount() {
Object max = getAttributes().get("max");
return max == null ? 5 : ((Number) max).intValue();
}
/** One item per star: "1 star", "2 stars", and so on. */
public List<SelectItem> getItems() {
List<SelectItem> items = new ArrayList<>();
for (int stars = 1; stars <= getStarCount(); stars++) {
items.add(new SelectItem(stars, stars + (stars == 1 ? " star" : " stars")));
}
return items;
}
/** Value-change listener of the inner radio group: tells the page author's onRated method. */
public void valueChanged(ValueChangeEvent event) {
MethodExpression onRated = (MethodExpression) getAttributes().get("onRated");
if (onRated != null && event.getNewValue() != null) {
onRated.invoke(FacesContext.getCurrentInstance().getELContext(), new Object[] {event.getNewValue()});
}
}
/** Validator of the inner radio group; it needs the max attribute, so it lives here. */
public void validateRange(FacesContext context, UIComponent input, Object value) {
if (value instanceof Integer stars && (stars < 1 || stars > getStarCount())) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Choose between 1 and " + getStarCount() + " stars.", null));
}
}
}
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:f="jakarta.faces.core"
xmlns:h="jakarta.faces.html"
xmlns:cc="jakarta.faces.composite">
<cc:interface componentType="bookshelf.StarRating">
<cc:attribute name="value" type="java.lang.Integer" required="true"/>
<cc:attribute name="max" type="java.lang.Integer" default="5"/>
<cc:attribute name="label" type="java.lang.String" default="Rating"/>
<cc:attribute name="required" type="java.lang.Boolean" default="false"/>
<cc:attribute name="readonly" type="java.lang.Boolean" default="false"/>
<cc:attribute name="onRated" method-signature="java.lang.String rated(java.lang.Integer)"/>
<cc:editableValueHolder name="stars"/>
<cc:clientBehavior name="change" event="change" targets="stars" default="true"/>
</cc:interface>
<cc:implementation>
<h:outputStylesheet library="bookshelf" name="starRating.css" target="head"/>
<fieldset id="#{cc.clientId}" class="star-rating">
<legend>#{cc.attrs.label}</legend>
<h:selectOneRadio id="stars" value="#{cc.attrs.value}" label="#{cc.attrs.label}"
required="#{cc.attrs.required}" disabled="#{cc.attrs.readonly}"
validator="#{cc.validateRange}" valueChangeListener="#{cc.valueChanged}"
layout="lineDirection">
<f:selectItems value="#{cc.items}"/>
</h:selectOneRadio>
<h:message for="stars"/>
</fieldset>
</cc:implementation>
</ui:component>
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Review</title>
</h:head>
<h:body>
<h:form id="review">
<h:messages globalOnly="true"/>
<bs:starRating id="rating" label="Your rating" value="#{reviewBean.rating}" required="true" onRated="#{reviewBean.rated}">
<f:validateLongRange for="stars" minimum="2"/>
<f:ajax render="summary"/>
</bs:starRating>
<bs:starRating id="difficulty" label="Difficulty" value="#{reviewBean.difficulty}" max="3"/>
<h:outputLabel for="comment" value="Comment"/>
<h:inputTextarea id="comment" value="#{reviewBean.comment}"/>
<h:outputText id="summary" value="You rated this #{reviewBean.rating} out of 5 (last pick: #{reviewBean.lastRated})."
rendered="#{reviewBean.rating != null}"/>
<h:commandButton id="save" value="Save review" action="#{reviewBean.save}"/>
</h:form>
</h:body>
</html>
Source: Jakarta EE Tutorial: Composite Components: Advanced Topics and an Example and Jakarta Faces 4.1 specification, 3.6.2.1 Composite Component Metadata
How it works, with the results from both implementations:
-
#{cc.items}and#{cc.validateRange}are properties and methods of the backing component, becauseccis the composite root and the root is aStarRating. A page withmax="3"rendered three radio buttons and a tampered post of5for it was answered withChoose between 1 and 3 stars.(and, since the bean has@Max(3), also with Bean Validation’s own message; MyFaces prefixed it with the label,Difficulty: must be less than or equal to 3). -
onRatedis a method attribute with amethod-signaturewhose name is not special. The composite stores theMethodExpressionin the component’s attribute map, and the backing component calls it fromvalueChanged, thevalueChangeListenerof the inner radio group. A pick of4leftreviewBean.lastRatedat4on both (the review page showslast pick: 4). -
The getter of the star count is named
getStarCount, notgetMax. That is deliberate, and it is a trap.getAttributes().get("max")first looks for a bean property calledmaxon the component, so agetMax()that itself callsgetAttributes().get("max")recurses forever. The first version of this class hadgetMax()and Mojarra answered the page withjava.lang.StackOverflowError; MyFaces failed withjakarta.el.ELException: Error reading [items] on type [org.example.bookshelf.web.StarRating]. Name helper getters differently from the attributes they read. -
getItems()is evaluated while the component renders and may be evaluated more than once per request. Keep it cheap and free of side effects, as the data tables and backing bean pages advise for all getters used from a view. -
Do not invoke a method attribute while the composite is rendering. An earlier attempt let
getItems()call a method attributelabelForto produce the label of each item. The first evaluation worked, and the next#{cc...}evaluation in the same render then failed withPropertyNotFoundException: ELResolver did not handle type: [null] with property of [items]on both Mojarra and MyFaces: after the call,ccno longer resolved. Invoke method attributes from listeners and actions, asvalueChangeddoes, and pass data that is needed for rendering as ordinary value attributes.
A Composite Whose Root Is an Input
The root of a composite does not have to be a plain naming container. It can extend UIInput and implement NamingContainer,
with the family jakarta.faces.NamingContainer, so that required, validator, converter and label on the tag apply to the
composite itself instead of to a target. The root collects the inner input’s submitted value:
package org.example.bookshelf.web;
import jakarta.faces.component.FacesComponent;
import jakarta.faces.component.NamingContainer;
import jakarta.faces.component.UIInput;
import jakarta.faces.component.UINamingContainer;
import jakarta.faces.context.FacesContext;
/** A composite root that is itself an input, so required, validator and converter apply to the tag. */
@FacesComponent("bookshelf.RatingInput")
public class RatingInput extends UIInput implements NamingContainer {
@Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
private UIInput radios() {
return (UIInput) findComponent("stars");
}
/** The submitted value lives in the inner radio group. */
@Override
public Object getSubmittedValue() {
return radios().getSubmittedValue();
}
@Override
public void setSubmittedValue(Object submittedValue) {
radios().setSubmittedValue(submittedValue);
}
@Override
protected Object getConvertedValue(FacesContext context, Object submittedValue) {
String text = submittedValue == null ? "" : submittedValue.toString();
return text.isBlank() ? null : Integer.valueOf(text);
}
@Override
public void resetValue() {
super.resetValue();
radios().resetValue();
}
}
<ui:component xmlns:ui="jakarta.faces.facelets"
xmlns:f="jakarta.faces.core"
xmlns:h="jakarta.faces.html"
xmlns:cc="jakarta.faces.composite">
<cc:interface componentType="bookshelf.RatingInput">
<cc:attribute name="value" type="java.lang.Integer"/>
</cc:interface>
<cc:implementation>
<h:selectOneRadio id="stars" value="#{cc.value}">
<f:selectItem itemValue="1" itemLabel="1 star"/>
<f:selectItem itemValue="2" itemLabel="2 stars"/>
<f:selectItem itemValue="3" itemLabel="3 stars"/>
</h:selectOneRadio>
</cc:implementation>
</ui:component>
<!DOCTYPE html>
<html xmlns:h="jakarta.faces.html"
xmlns:bs="jakarta.faces.composite/bookshelf">
<h:head>
<title>Rating input</title>
</h:head>
<h:body>
<h:form id="review">
<bs:ratingInput id="rating" value="#{reviewBean.rating}" required="true"
label="Rating" requiredMessage="Please pick a rating."/>
<h:message for="rating"/>
<h:commandButton id="save" value="Save" action="#{reviewBean.save}"/>
</h:form>
</h:body>
</html>
The result differed. On Mojarra, posting nothing gave Please pick a rating. (the requiredMessage on the tag). On MyFaces 4.1.4
the same post was accepted, with no message and the action ran, so the required of the tag was not enforced on the root. In a
scratch variant with a validator attribute on the tag, the validator method was called on Mojarra and never on MyFaces. I did not find out why. The portable design is the one used by the star rating: leave the root a UINamingContainer,
put the validation on the inner input and expose it with cc:editableValueHolder. Treat the UIInput root as Mojarra-only until
you have tested your implementation.
Validating Composite Values
Validation of a composite happens on its inner input components, with the standard rules of the validation page.
The tutorial lists the validator tags that can be used with cc:valueHolder and cc:editableValueHolder: f:validateBean,
f:validateRegex and f:validateRequired, and any other validator tag with a for attribute works the same way. Four layers
were tested on the star rating, and they combine:
| Layer | How and where |
|---|---|
Required |
|
Attached validators |
|
The composite’s own validator |
|
Bean Validation |
The constraints on |
Messages need a place. The star rating shows its own <h:message for="stars"/> inside the composite, so every use of the
component has the message next to it, and the page can additionally show h:messages. When both are present the same message
appears twice, which was visible in the output (the inner message and the list). Choose one place. A page that wants the messages
elsewhere can omit the inner h:message and use <h:messages for=...> or a message list, since the client id of the inner input is
predictable (review:rating:stars). The messages page covers the rest, and the converters page the for
attribute of f:convertNumber and friends, which attaches to a cc:valueHolder in the same way.
The immediate cancel button in reviewButtons is how a composite offers "leave without validating": the lifecycle page
explains what an immediate command skips and why (the immediate attribute).
Ajax in and Around Composites
Two kinds of f:ajax meet a composite, and they resolve ids from different places.
-
Inside the implementation. An
f:ajaxnested in an inner component is part of the composite, so the ids in itsrenderandexecuteare relative to the composite, which is a naming container. I testedrender="@this"in a listener-driven variant of the star rating (theonRatedcall above ran from it) on both implementations. The version-2 star rating does not use such a tag, because the page author owns the Ajax behaviour throughcc:clientBehavior. -
In the using page. An
f:ajaxnested inside the composite tag is attached throughcc:clientBehavior, and itsrenderandexecuteids are resolved from the using page, relative to the naming container that surrounds the tag (the form).<f:ajax render="summary"/>addressed theh:outputText id="summary"next to the component, as in the version-1 review page, and the generated call carried the client idreview:summary.
The page on Ajax covers the attributes of f:ajax, the search expressions and the partial lifecycle in detail.
cc:extension and Other Deprecations
cc:extension is deprecated in Faces 4.1 without replacement and is planned for removal in Faces 5.0 (issue 1871 on the 5.0 plan). The VDL gives the reason: the
JSR-276 metadata it was meant to carry went dormant, so the tag was never of use. A composite file that contains
<cc:extension><meta-info/></cc:extension> loaded on both implementations; MyFaces 4.1.4 logged
WARNING: The tag composite:extension is deprecated as of 4.1. and Mojarra 4.1.17 logged nothing about it. Delete the tag.
In the API, ActionSource2AttachedObjectTarget is marked @Deprecated(since = "4.1", forRemoval = true) with the advice to use
ActionSourceAttachedObjectTarget instead (a class that exists since 4.1 and, according to its documentation, is the
former class with the 2 dropped, because ActionSource2 itself is deprecated). The 4.1 change log lists "Unify ActionSource
and ActionSource2" (issue 1832). Application code almost never uses these interfaces; they matter to tool and framework authors.
Mojarra and MyFaces Compared
Everything else on this page behaved the same. The differences found:
| Situation | Mojarra 4.1.17 | MyFaces 4.1.4 |
|---|---|---|
|
Error |
Rendered |
Required attribute missing |
|
|
Required facet missing |
|
|
|
Page error |
Rendered without error, no Ajax behaviour |
|
|
Page rendered |
|
|
Includes internal keys |
|
No log message |
|
Bean Validation message in a composite input |
Message only |
Message prefixed with the label, |
Composite root extending |
Enforced |
Not enforced in this run |
Generated ids in a repeat |
|
|
The generated ids and the format of the error pages are implementation details, not contracts.
What Changed per Version
-
2.0 (JSR 314, 2009): composite components, the
ccimplicit object,cc.attrs, attached objects, thecc:tag library and the resource-library based lookup are introduced with Facelets as the standard view declaration language. -
2.2: the
http://xmlns.jcp.org/jsf/compositenamespace replaceshttp://java.sun.com/jsf/composite. The change log deprecates theCURRENT_COMPONENTandCURRENT_COMPOSITE_COMPONENTattributes (issue 784). -
3.0:
javax.facesbecomesjakarta.faces; the namespaces of the markup did not change yet. -
4.0: the URN namespaces
jakarta.faces.compositeandjakarta.faces.composite/<library>are introduced. The oldhttp://xmlns.jcp.org/jsf/compositeform still rendered a composite on Mojarra 4.1.17 and MyFaces 4.1.4. -
4.1:
cc:extensionis deprecated (issue 1549);ActionSourceandActionSource2are unified (issue 1832), which deprecatesActionSource2AttachedObjectTargetfor removal in favour ofActionSourceAttachedObjectTarget; the change log also lists improvedf:ajaxbehaviour in composite components (issue 1567; I did not find a description of the change and cannot say what behaviour differs, other than what the table above shows). -
5.0 (upcoming): the Faces 5.0 specification page (under development on 2026-10-08) lists, among its planned removals, "Remove deprecated code (composite:extension, …)" (issue 1871) and "Remove support for old xml namespaces" (issue 2105). These are planned changes for an unreleased version, not behaviour to design for.
Where the Books Are Outdated
JavaServer Faces 2.0: The Complete Reference (Burns and Schalk) and the JSF 2.0 Cookbook (Leonard) cover composite components,
including attribute and behaviour retargeting, backing components and Ajax-enabled composites, so the concepts on this page are
the ones they introduce and the model has not changed. What has changed is the surface. Their examples use the javax.faces
packages, the JSF 2.0 namespace http://java.sun.com/jsf/composite/<library> and,
where a bean is needed, @ManagedBean. Today the namespace is jakarta.faces.composite/<library>, the package is jakarta.faces, and
the bean is a CDI @Named bean.
Facelets Essentials (Aranda and Wadia) predates composite components: its "composition components" and tag files are the
Facelets 1.x ancestors, and the reusable-fragment techniques in the book (the ui:composition and ui:decorate family)
are covered on the templating page. A composite component is the 2.0 successor, with real attributes, a client id and attached objects.
Mastering JavaServer Faces (Dudney et al.) talks about the Composite design pattern, which describes the component tree
(a component made of components), and is a different thing from the cc: feature of the specification. Both ideas
meet in the composite component, but the book’s pattern chapter has no cc:interface. The JSP-based books
(Mann, Dudney, Jacobi and Fallows) build reusable components as Java classes with JSP tag handlers, which Faces 4 no longer supports; the
current replacement for the simple cases is a composite component, and for renderer-level control the custom component with a
Facelets tag, on the page about custom components.
Pitfalls
-
Looking for markup that is not there. The composite renders nothing of its own. Put
id="#{cc.clientId}"on the wrapper element if CSS, JavaScript or a label needs to find it. -
Reading an attribute with a name equal to a bean property of the backing component.
getAttributes().get("max")plus agetMax()that calls it is an infinite recursion (StackOverflowErroron Mojarra; anELExceptionon MyFaces). -
Calling a method attribute during rendering. It made later
ccexpressions fail on both implementations (see The Bookshelf Star Rating, Version 2: a Backing Component). Call it from a listener. -
Typos in
for,name,targets. They are silently ignored in several cases (see Attached Objects: Letting the Page Author Reach Inner Components), so a validator that "does nothing" is usually a wrong name. Test the unhappy path. -
A wrong
targetsin a nested naming container. The id is relative to the composite and includes the container prefix. -
Missing
cc:interface. Mojarra rejects it; write<cc:interface/>(see cc:interface and cc:implementation). -
Making the root a
UIInput. Works on Mojarra, not on MyFaces (see A Composite Whose Root Is an Input). -
Binding the attribute to a literal.
value="3"is read-only; the bean never changes (see cc:attribute). -
Iterating
cc.attrs. Not portable (see The cc Implicit Object). -
Putting many messages in the composite. A
h:messageinside the composite plus anh:messageson the page shows each message twice. -
Heavy work in getters of the backing component. They run for each render and sometimes several times in one.
-
Using JSTL tags and build-time attributes inside a composite and expecting render-time behaviour.
c:ifincc:implementationis evaluated when the view is built, as everywhere else (the page on JSTL and build time versus render time explains the rule). -
Trusting the interface. The contract does not stop a user from posting any value for an inner input (the version-2 star rating validated a tampered
5againstmax="3"on the server for that reason). Validate on the server with validators and constraints (reasoning, plus the tampered post described above).
References
-
Jakarta Faces 4.1 specification, 3.6 Composite User Interface Components
-
Jakarta Faces 4.1 specification, 3.6.2.1 Composite Component Metadata
-
Jakarta Faces 4.1 specification, 10.3.3 Requirements specific to composite components
-
Jakarta Faces 4.1 specification, 10.4.4 Composite Component Tag Library
-
Jakarta Faces 4.1 specification, 3.5.6 Bean Validation Integration
-
Jakarta EE Tutorial: Composite Components: Advanced Topics and an Example
-
Jakarta EE Tutorial: Using Ajax with Jakarta Faces Technology
-
Burns, Schalk: JavaServer Faces 2.0: The Complete Reference, McGraw-Hill, 2010, publisher page
-
Leonard: JSF 2.0 Cookbook, Packt, 2010, publisher page
-
Aranda, Wadia: Facelets Essentials, Apress, 2008, publisher page
-
Dudney, Lehr, Willis, Mattingly: Mastering JavaServer Faces, Wiley, 2004, publisher page