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 UIComponent (by default a UINamingContainer) whose children come from the XHTML file

A template that is inlined into the including view; no component of its own

A Java class extending UIComponent, usually with a Renderer

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 *.taglib.xml file and FACELETS_LIBRARIES or a META-INF file

Annotations or faces-config.xml, plus a tag

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:

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:interface declares two attributes. book is required and is typed with a class name; showYear is optional, typed java.lang.Boolean, and defaults to true.

  • cc:implementation is ordinary Facelets markup. The attributes are read through the cc implicit object: #{cc.attrs.book.title} is the title property of whatever the page author passed as book.

  • #{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:

catalogue.xhtml
<!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):

web/CatalogueBean.java
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

src/main/webapp/resources/<library>/<name>.xhtml

Components of the web application (observed: resources/bookshelf/bookCard.xhtml)

META-INF/resources/<library>/<name>.xhtml inside a JAR in WEB-INF/lib

Reusable component libraries shipped as a JAR (observed with a JAR holding META-INF/resources/ui/hello.xhtml and the namespace jakarta.faces.composite/ui; Mojarra and MyFaces rendered it)

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:

WEB-INF/bookshelf.taglib.xml
<?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):

WEB-INF/web.xml (fragment)
  <context-param>
    <param-name>jakarta.faces.FACELETS_LIBRARIES</param-name>
    <param-value>/WEB-INF/bookshelf.taglib.xml</param-value>
  </context-param>
j-taglib.xhtml
<!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>

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):

  1. 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()).

  2. For the tag <bs:starRating> it calls Application.createComponent(FacesContext, Resource). That method asks the view declaration language for the composite metadata (ViewDeclarationLanguage.getComponentMetadata(), a BeanInfo built from the cc:interface), then decides which Java class to instantiate, in this order: the componentType declared on cc:interface; a script component for the resource; a Java class called <library>.<file name without extension>; and otherwise the default, a UINamingContainer (component type jakarta.faces.NamingContainer). The renderer type of the new component is set to jakarta.faces.Composite.

  3. The children of the file’s cc:implementation are put in a facet of the new component, the facet named by the constant UIComponent.COMPOSITE_FACET_NAME, under a UIPanel with the renderer jakarta.faces.Group.

  4. 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 that cc:interface exposed, and the method expressions (see Method Attributes and Retargeting) to the inner components' action, actionListener, validator and valueChangeListener properties.

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)

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):

resources/bookshelf/probe.xhtml
<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>
probe.xhtml
<!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>

Rendered (the same on Mojarra and MyFaces, ?n=5&d=Z appended for the third card in the second line):

Expression Value observed

cc.attrs.req

x; with req="#{param.n}" the parameter, empty when absent

cc.id

p1, the id the page author gave

cc.clientId

f:p1: form id, separator, composite id

cc.family

jakarta.faces.NamingContainer

cc.rendererType

jakarta.faces.Composite

cc.facets.keySet()

[jakarta.faces.component.COMPOSITE_FACET_NAME]

cc.attrs.keySet()

Mojarra: UnsupportedOperationException (the attribute map cannot be enumerated). MyFaces: a set that includes internal keys such as jakarta.faces.component.BEANINFO_KEY besides the attributes. Do not iterate over cc.attrs.

cc:interface and cc:implementation

A composite file has at most one cc:interface and at most one cc:implementation, in that order.

  • cc:interface declares 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 are componentType (the component type of the root, which must be in the family jakarta.faces.NamingContainer; see Backing Components) and the tooling metadata name, displayName, shortDescription, expert, hidden and preferred. name is advisory only: the real name is the file name. The specification adds that displayName, shortDescription, expert, hidden and preferred are exposed to tools only when the project stage is Development.

  • cc:implementation is 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:interface is present, a cc:implementation must accompany it. The VDL documentation says that without a cc:interface the 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

name (required)

The attribute name on the tag. The values action, actionListener, validator and valueChangeListener are special: they are retargeted as described in Method Attributes and Retargeting.

type

The class the value must be assignable to, as a fully qualified name. Default java.lang.Object. Mutually exclusive with method-signature; if both are present method-signature is ignored.

default

The value to use when the page author does not supply one.

required

true if the page author must supply a value. Default false.

method-signature

Declares the attribute a MethodExpression; the value is a Java signature with fully qualified names, such as java.lang.String action() or java.lang.String rated(java.lang.Integer).

targets

With a method-signature, a space-separated list of ids (relative to the composite) of the inner components that receive the method expression.

targetAttributeName

The attribute of the inner component that receives the method expression, when it differs from name.

displayName, shortDescription, expert, hidden, preferred

Tooling metadata, set on the composite’s BeanDescriptor.

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"/> without req failed 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, typed read 7 and def read D. The default was also used when the supplied expression evaluated to null: def="#{param.d}" with no d parameter rendered D, and typed="#{param.n}" with no n rendered 7. With n=5 and d=Z the supplied values won.

  • Type. With type="java.lang.Integer", typed="#{param.n}" and n=5 read 5 and #{cc.attrs.typed + 1} gave 6; typed="41" gave 41. An empty parameter (n=) was coerced to 0 by 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: type is an Expression Language coercion, not a Faces validator.

  • Undeclared attributes. extra="E" was not declared, yet #{cc.attrs.extra} read E. 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:

resources/bookshelf/starRating.xhtml
<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>
  • value is the rating (an Integer), label names the group, and required and readonly are switches. They are passed straight to the radio group, so the standard required behaviour applies.

  • cc:editableValueHolder name="stars" exposes the radio group, whose id is stars, as a target for converters, validators and value-change listeners. cc:clientBehavior does the same for Ajax (Attached Objects: Letting the Page Author Reach Inner Components).

  • The label of 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:outputStylesheet inside cc:implementation registers 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.

resources/bookshelf/starRating.css
.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:

review.xhtml (version 1)
<!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>
web/ReviewBean.java
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; }
}

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

review:rating:stars=1

Your rating: Validation Error: Value is less than allowable minimum of '2', from the f:validateLongRange the page attached; the bean’s @Min(1) passed

review:rating:stars=4

Model updated, Thanks, your review was saved., You rated this 4 out of 5.

nothing for the rating

Your rating: Validation Error: Value is required. (required="true" passed through #{cc.attrs.required})

Ajax change from the radio group

Only the summary text was updated: You rated this 3 out of 5.

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

cc:valueHolder

ValueHolder

f:converter, f:convertNumber, f:convertDateTime

cc:editableValueHolder

EditableValueHolder

The above plus f:validator, f:validateLength, f:validateLongRange, f:validateRegex, f:validateRequired, f:validateBean and f:valueChangeListener

cc:actionSource

ActionSource (see What Changed per Version for ActionSource2)

f:actionListener, f:setPropertyActionListener

cc:clientBehavior

ClientBehaviorHolder

f:ajax and other client behaviors; the page author names the event with event

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:

resources/bookshelf/amount.xhtml
<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>
fines.xhtml
<!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 for is 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:ajax needs a cc: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 a cc:clientBehavior without default="true" and the f:ajax omitted event. MyFaces rendered the page without error and, by inspection of the output, without Ajax on the component.

  • for names the name of the target, not the id of an inner component. They are the same only when targets is 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:

resources/bookshelf/reviewButtons.xhtml
<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>
buttons.xhtml
<!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:

web/ReviewAudit.java
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

review:comment=hi and review:buttons:save=Save

reviewBean.save ran (Thanks, your review was saved.); reviewBean.saveClicked saw the click (clicked=review:buttons:save); the f:actionListener for="save" listener logged AUDIT save clicked on review:buttons:save. The actionListener attribute and the attached listener both ran on the same button.

empty comment and review:buttons:cancel=Cancel

reviewBean.cancel ran and the required comment was not checked: the inner cancel button is immediate

empty comment and review:buttons:save=Save

review:comment: Validation Error: Value is required., no action

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:

resources/bookshelf/ratingRow.xhtml
<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:

resources/bookshelf/quickNote.xhtml
<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>
nesting.xhtml
<!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

cc:insertChildren

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.

cc:facet (in cc:interface)

Declares a facet the page author may supply with f:facet. Attributes: name, required, and the tooling metadata.

cc:renderFacet (in cc:implementation)

Renders the named facet at this point of the output. The runtime inserts a component with renderer type jakarta.faces.CompositeFacet. required="true" throws a TagException if the page author did not supply it.

cc:insertFacet (in cc:implementation)

Takes the named facet of the composite and inserts it as a facet child of the component in which this tag is nested. required="true" behaves as for renderFacet.

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:

resources/bookshelf/panel.xhtml
<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>
reviews-panel.xhtml
<!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 child hint both landed at cc:insertChildren, and the component kept its composite-prefixed id shelf:reviews:hint.

  • A missing required facet is a build-time error: Mojarra The following facets(s) are required, but no facets have been supplied for them: footer., MyFaces The 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:insertFacet must be nested in a component that renders that facet. Put directly in a h:panelGroup, whose renderer has no slot called footer, 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

value

The component type, the name used in componentType. When empty it defaults to the class’s simple name with the first letter in lower case.

createTag, tagName, namespace

When createTag is true, Faces also creates a Facelets tag for the component, in namespace (default jakarta.faces.component, constant FacesComponent.NAMESPACE) with the name tagName. Used by custom components, not by composites.

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:

src/main/java/bookshelf/conv.java
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();
    }
}
resources/bookshelf/conv.xhtml
<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.

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:

web/StarRating.java
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));
        }
    }
}
resources/bookshelf/starRating.xhtml (version 2)
<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>
review.xhtml (version 2)
<!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>

How it works, with the results from both implementations:

  • #{cc.items} and #{cc.validateRange} are properties and methods of the backing component, because cc is the composite root and the root is a StarRating. A page with max="3" rendered three radio buttons and a tampered post of 5 for it was answered with Choose 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).

  • onRated is a method attribute with a method-signature whose name is not special. The composite stores the MethodExpression in the component’s attribute map, and the backing component calls it from valueChanged, the valueChangeListener of the inner radio group. A pick of 4 left reviewBean.lastRated at 4 on both (the review page shows last pick: 4).

  • The getter of the star count is named getStarCount, not getMax. That is deliberate, and it is a trap. getAttributes().get("max") first looks for a bean property called max on the component, so a getMax() that itself calls getAttributes().get("max") recurses forever. The first version of this class had getMax() and Mojarra answered the page with java.lang.StackOverflowError; MyFaces failed with jakarta.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 attribute labelFor to produce the label of each item. The first evaluation worked, and the next #{cc...} evaluation in the same render then failed with PropertyNotFoundException: ELResolver did not handle type: [null] with property of [items] on both Mojarra and MyFaces: after the call, cc no longer resolved. Invoke method attributes from listeners and actions, as valueChanged does, 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:

web/RatingInput.java
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();
    }
}
resources/bookshelf/ratingInput.xhtml
<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>
rating-input.xhtml
<!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

required on the inner input, supplied by a composite attribute (required="#{cc.attrs.required}") so that the page author decides. Outcome with nothing selected: Your rating: Validation Error: Value is required.

Attached validators

<f:validateLongRange for="stars" minimum="2"/> in the using page, through cc:editableValueHolder. The page author chooses the rule without knowing the inner structure.

The composite’s own validator

validator="#{cc.validateRange}" on the inner input, a method of the backing component. It can read the composite’s attributes, such as max, which a plain validator class would have to look up with UIComponent.getCompositeComponentParent().

Bean Validation

The constraints on ReviewBean.rating (@Min(1) @Max(5)) were enforced through value="#{cc.attrs.value}" on both: the inner input’s value expression resolves to the bean property, so the default Bean Validation validator applies. A value of 7 that reached the bean (one of my runs offered ten stars) was rejected with the bean’s message Five stars is the maximum.

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:ajax nested in an inner component is part of the composite, so the ids in its render and execute are relative to the composite, which is a naming container. I tested render="@this" in a listener-driven variant of the star rating (the onRated call 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 through cc:clientBehavior.

  • In the using page. An f:ajax nested inside the composite tag is attached through cc:clientBehavior, and its render and execute ids are resolved from the using page, relative to the naming container that surrounds the tag (the form). <f:ajax render="summary"/> addressed the h:outputText id="summary" next to the component, as in the version-1 review page, and the generated call carried the client id review: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

cc:implementation without cc:interface

Error Unable to find interface for implementation.

Rendered

Required attribute missing

The following attribute(s) are required, but no values have been supplied for them: req.

Attribute 'req' is required

Required facet missing

The following facets(s) are required, but no facets have been supplied for them: footer.

The following facets are required by the component: [footer]

f:ajax on a composite with no matching cc:clientBehavior (or without default="true" and without event)

Page error enclosing composite component does not support event null

Rendered without error, no Ajax behaviour

targets naming an id that is not found because of a missing naming container

IllegalArgumentException at view build

Page rendered

cc.attrs.keySet()

UnsupportedOperationException

Includes internal keys

cc:extension

No log message

WARNING: The tag composite:extension is deprecated as of 4.1.

Bean Validation message in a composite input

Message only

Message prefixed with the label, Your rating: Five stars is the maximum.

Composite root extending UIInput (required, validators)

Enforced

Not enforced in this run

Generated ids in a repeat

j_idt5

j_id_7

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 cc implicit object, cc.attrs, attached objects, the cc: 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/composite namespace replaces http://java.sun.com/jsf/composite. The change log deprecates the CURRENT_COMPONENT and CURRENT_COMPOSITE_COMPONENT attributes (issue 784).

  • 3.0: javax.faces becomes jakarta.faces; the namespaces of the markup did not change yet.

  • 4.0: the URN namespaces jakarta.faces.composite and jakarta.faces.composite/<library> are introduced. The old http://xmlns.jcp.org/jsf/composite form still rendered a composite on Mojarra 4.1.17 and MyFaces 4.1.4.

  • 4.1: cc:extension is deprecated (issue 1549); ActionSource and ActionSource2 are unified (issue 1832), which deprecates ActionSource2AttachedObjectTarget for removal in favour of ActionSourceAttachedObjectTarget; the change log also lists improved f:ajax behaviour 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 a getMax() that calls it is an infinite recursion (StackOverflowError on Mojarra; an ELException on MyFaces).

  • Calling a method attribute during rendering. It made later cc expressions 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 targets in 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:message inside the composite plus an h:messages on 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:if in cc:implementation is 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 5 against max="3" on the server for that reason). Validate on the server with validators and constraints (reasoning, plus the tampered post described above).

References